文章目录
- 流程伪代码
- 代码实现
- 加载库
- 创建套接字
- 绑定ip地址和端口号
- 监听
- 接受连接
- 收发数据
- 关闭套接字、卸载库
流程伪代码
//1、加载库——WSAStartup()
//2、创建套接字——socket()
//3、绑定ip和端口号——bind()
//4、监听——listen()
while(true){
//5、接受连接——accept()
while(true){
//6、接收数据——recv()
//7、发送数据——send()
}
}
//8、关闭套接字、卸载库——closesocket()、WSACleanup()
代码实现
加载库
加载库和UDP一样,不用过多解释
int err = 0;
WORD version = MAKEWORD(2, 2);
WSADATA wsaData;
err = WSAStartup(version, &wsaData);
if (0 != err) {
cout << "WSAStartup error" << WSAGetLastError() << endl;
return 1;
}
if (2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion)) {
cout << "WSAStartup version error" << endl;
WSACleanup();
return 1;
}
else {
cout << "WSAStart success" << endl;
}
创建套接字
思路和UDP也相同,只不过参数有所变化
SOCKET sock = socket(AF_INET,SOCK_STREAM , IPPROTO_TCP);
if (INVALID_SOCKET == sock) {
cout << "socket error" << WSAGetLastError() << endl;
WSACleanup();
return 1;
}
else {
cout << "socket success" << endl;
}
绑定ip地址和端口号
绑定也和UDP中的一样
sockaddr_in addrServer;
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(456789);
addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
err = bind(sock, (sockaddr*)&addrServer, sizeof(addrServer));
if (SOCKET_ERROR == err) {
cout << "bind error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return 1;
}
else {
cout << "bind success" << endl;
}
监听
监听我们用listen()函数进行,他有两个参数,第一个为socket,意为派哪个socket去进行监听,第二个参数为int值,它是能够等待被连接的队列的最大长度,返回值如果是0就没有问题,为SOCKET_ERROR就报错
err = listen(sock, 10);
if (SOCKET_ERROR == err) {
cout << "listen error:" << WSAGetLastError() << endl;
closesocket(sock);
WSACleanup();
return 1;
}
else {
cout << "listen success" << endl;
}
接受连接
接受连接我们使用accept()函数,它的返回值为SOCKET,当创建连接成功时会返回一个新的socket,因为之前我们创建的socket去进行监听了,所以我们需要再创建一个socket来接这个返回值。它的第一个参数为socket,就是我们用来进行监听的那个socket,进行监听的这个socket就相当于酒店迎宾的,而返回的socket就相当于是服务员。第二个参数为sockaddr*类型的输出参数,它用来装对端的地址信息,第三个参数就是这个输出参数的长度。
sockaddr_in addrClient;
int addrClientSize = sizeof(addrClient);
//接受连接
SOCKET sockTalk = accept(sock, (sockaddr*)&addrClient, &addrClientSize);
if (INVALID_SOCKET != sockTalk) {
//打印客户端的IP地址
cout << "Client ip:" << inet_ntoa(addrClient.sin_addr) << endl;
}
else {
cout << "accept error:" << WSAGetLastError() << endl;
break;
}
收发数据
当连接成功之后就可以进行收发数据了
接收数据我们使用recv()函数,这个函数相比recvfrom()少了两个参数,它不需要记录对端的地址信息了,因为已经跟对端连接成功了,已经知道对端的地址信息了,连接成功返回的socket就像是连接在两端的一条绳。只能给连接成功的两端使用,如果别人想使用,那么需要跟另外一端重新建立连接,也就是说想跟谁通信就用跟谁连接产生的socket
int nRecvNum = 0;
char recvBuf[1024] = "";
nRecvNum = recv(sockTalk, recvBuf, sizeof(recvBuf), 0);
if (nRecvNum > 0) {
cout << "Client say:" << recvBuf << endl;
}
else {
cout << "recv error:" << WSAGetLastError() << endl;
break;
}
发送数据我们使用send()函数,原理和recv()函数相同
int nSendNum = 0;
char sendBuf[1024] = "";
gets_s(sendBuf);
nSendNum = send(sockTalk, sendBuf, sizeof(sendBuf), 0);
if (SOCKET_ERROR == nSendNum) {
cout << "send error:" << WSAGetLastError() << endl;
break;
}
关闭套接字、卸载库
当我们结束与这个客户端聊天时,我们应该关闭与这个客户端连接时产生的套接字(外层循环里面进行)
closesocket(sockTalk);
最终关闭监听用的sock然后卸载库
closesocket(sock);
WSACleanup();
这样我们的服务端就写完了