Windows网络通信(一):socket同步编程
网络通信常用API
1. WSAStartup用于初始化WinSock环境
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );
wVersionRequested:当前进程能够使用Windows Socket的最高版本,目前指定2.2即可。
lpWSAData:指向一个WSAData结构体,接受Socket详细信息。
成功返回0
2. socket建立一个指定类型的SOCKET用于通信
SOCKET WSAAPI socket( int af, int type, int protocol );
af:address family,指定用于通信的网络地址类型,可以取值AF_INET(IPv4),AF_INET6(IPv6),AF_BTH(蓝牙)等。
type:指定传输类型,可以取值SOCK_STREAM(用于TCP),SOCK_DGRAM(用于UDP)等。
protocol:通信协议,可以取值IPPROTO_TCP,IPPROTO_UDP等。
成功返回一个可以用于通信的SOCKET,否则返回INVALID_SOCKET。
3. bind将socket和网络地址和端口绑定起来
int bind( SOCKET s, const struct sockaddr *name, int namelen );
s:一个未绑定的socket。
name:指向一个sockaddr对象,用于指定绑定的ip和端口信息。
namelen:sockaddr的长度,为什么这里还需要指定长度呢,因为name是根据socket的类型来指定不同的结构体的,可能是sockaddr_in(IPv4)或者sockaddr_in6(IPv6)。
成功返回0
4. listen将SOCKET设为监听状态,可以被客户端连接
int listen( SOCKET s, int backlog );
s:一个未被连接的socket
backlog:可以连接的客户端的最大数目,如果指定为SOMAXCONN,则设置为最大的连接数量。
成功返回0
5. send通过指定socket发送数据
int send( SOCKET s, const char *buf, int len, int flags );
s:一个已经连接的socket。
buf:待发送数据
len:待发送数据的长度
flags:发送的一个标志设定,一般设为0
成功返回已发送的字节数目。这个数目可能小于len的。失败返回SOCKET_ERROR。
6. recv通过指定的socket接受数据
int recv( SOCKET s, char *buf, int len, int flags );
s:一个已经连接的socket
buf:接收数据的缓存区
len:缓存区长度
flags:接受数据的一个标志,一般设为0。
成功返回已接受数据的长度,失败返回SOCKET_ERROR,如果已经断开连接,返回0
7. shutdown关闭一个SOCKET的send或者recv功能
int shutdown( SOCKET s, int how );
s:socket
how:指定该socket的某个功能不需要再使用,可以取值SD_RECEIVE(接收功能),SD_SEND(发送功能),SD_BOTH(发送和接收功能)。
成功返回0,失败返回SOCKET_ERROR
8. connect连接到服务端,服务端开启listen后,客户端就可以使用connect进行连接
int connect( SOCKET s, const struct sockaddr *name, int namelen );
s:一个未连接的socket
name,namelen:和bind中name,namelen参数一样
成功返回0,失败返回SOCKET_ERROR
9. closesocket关闭一个已经存在的socket
int closesocket( SOCKET s );
s:一个待关闭的socket。
成功返回0,失败返回SOCKET_ERROR
10. accept接收一个来自客户端的连接
SOCKET accept( SOCKET s, struct sockaddr *addr, int *addrlen );
s:一个已经listen的socket
addr:用于储存接收到的客户端的sockaddr信息
addrlen:连接的客户端的sockaddr长度。
socket通信示例
服务端和客户端测试代码
// NetWork1.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <stdio.h> #include <tchar.h> #include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT 12345 //启动客户端 int startClient() { SOCKET ConnectSocket = INVALID_SOCKET; struct sockaddr_in clientService; char *sendbuf = "[Client]:客户端测试文本"; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN; // 创建一个TCP套接字 ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { printf("socket创建失败: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 指定连接端口和ip信息 clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(DEFAULT_PORT); // 连接服务端 iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService)); if (iResult == SOCKET_ERROR) { printf("连接失败: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } if (ConnectSocket == INVALID_SOCKET) { printf("无法连接到指定服务端!\n"); WSACleanup(); return 1; } // 发送一段数据 iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("发送数据失败: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("已发送数据大小: %ld\n", iResult); // 关闭发送功能,但是仍然可以接收 iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("关闭发送功能失败: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // 接收数据 do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if (iResult > 0) printf("已接收: %d\n", iResult); else if (iResult == 0) printf("连接关闭\n"); else printf("接收数据失败: %d\n", WSAGetLastError()); } while (iResult > 0); //关闭套接字 closesocket(ConnectSocket); WSACleanup(); return 0; } int startServer() { int iResult; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; sockaddr_in service; int iSendResult; char recvbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; char *sendbuf = "[Server]:服务端测试文本"; // 创建TCP套接字 ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket创建失败: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 设定绑定的ip和端口信息 service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(DEFAULT_PORT); // 绑定套接字 iResult = bind(ListenSocket, (SOCKADDR *)&service, sizeof(service)); if (iResult == SOCKET_ERROR) { printf("套接字绑定失败:%d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("套接字监听失败: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 接受来自客户端的连接 ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // 关闭服务端套接字,表示不需要再接收新的客户端连接了,但是已经连接的套接字还是能通信 closesocket(ListenSocket); do { //接收来自客户端的消息 iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); if (iResult > 0) { printf("已接收数据大小: %d\n", iResult); // 发送数据到客户端,这里就是将数据 iSendResult = send(ClientSocket, sendbuf, (int)strlen(sendbuf), 0); if (iSendResult == SOCKET_ERROR) { printf("发送失败: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } printf("发送字节大小: %d\n", iSendResult); } else if (iResult == 0) printf("连接已关闭\n"); else { printf("接收数据失败: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } } while (iResult > 0); //关闭套接字以及清理套接字环境 closesocket(ClientSocket); WSACleanup(); return 0; } int main(int argc, char *argv[]) { int iResult; WSADATA wsaData; //初始化套接字环境 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("初始化socket环境失败: %d\n", iResult); return 1; } if (argc == 2 && strcmp(argv[1], "c") == 0) { //客户端 return startClient(); } else if (argc == 2 && strcmp(argv[1], "s") == 0) { //服务端 return startServer(); } return 1; }
运行结果
后记
以上只是一个简单的socket通信示例,所有api调用都是阻塞的,非阻塞调用将在下文写出。