socket通信流程
tcp
server side:
socket()->bind()->listen()->accept()->send()/recv()->closesocket()
client:
socket()->connet()->send()/recv()->closesocket()
udp
server:
socket()->bind()->sendto()/recvfrom()->closesocket()
client:
socket()->sendto()/recvfrom()/closesocket()
winsock
库文件
tcp server
#ifndef WIN32_LEAN_AND_MEAN#define WIN32_LEAN_AND_MEAN //此宏用于预防 winsock.h 和winsock2.h 冲突#endif#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>#pragma comment(lib, "Ws2_32.lib")
tcpclient
#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdlib.h>#include <stdio.h>// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib#pragma comment (lib, "Ws2_32.lib")#pragma comment (lib, "Mswsock.lib")#pragma comment (lib, "AdvApi32.lib")
初始化winsock
使用winsock 编程前需要对winsock初始化,使用完毕后需要释放。
winsock初始化函数
int WSAStartup(WORD wVersionRequire,LPWSADATA lpWSAData);/*wVersionRequire 是winsock 初始换版本号 ,winsock有多个版本 常用 2.2lpWSAData 是一个指向WSAdata 的结构体指针函数返回值 0 成功 ,其他值 失败*/
winsock 释放函数
int WSACleanup(void);
示例代码
int iResult;//创建WSADATA 对象WSADATA wsaData;//...// 初始化winsockiResult = WSAStartup(MAKEWORD(2,2),&wsaData);if(iResult != 0){printf("WSAStart failed : %d",iResult);return 1;}
TCP
server
创建addrinfo
示例代码
#define DEFAULT_PORT "27015"struct addrinfo *result = NULL, *ptr = NULL, hints;ZeroMemory(&hints, sizeof (hints));hints.ai_family = AF_INET;hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;hints.ai_flags = AI_PASSIVE;// Resolve the local address and port to be used by the serveriResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);if (iResult != 0) {printf("getaddrinfo failed: %d\n", iResult);WSACleanup();return 1;}
socket创建与关闭
scoket(),closesocket()
SOCKET socket(int af,int type,int protocol);// af代表地址簇,//type 为套接字类型 stream , dgram,raw 代表流套接字,数据包套接字,原始协议接口//protocol 协议类型 tcp,udp,icmp ,etcint closesocket(SOCKET s);
示例代码
SOCKET ListenSocket = INVALID_SOCKET;// Create a SOCKET for the server to listen for client connectionsListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//检查是否有错if (ListenSocket == INVALID_SOCKET) {printf("Error at socket(): %ld\n", WSAGetLastError());freeaddrinfo(result);WSACleanup();return 1;}
绑定socket
// Setup the TCP listening socketiResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);if (iResult == SOCKET_ERROR) {printf("bind failed with error: %d\n", WSAGetLastError());freeaddrinfo(result);closesocket(ListenSocket);WSACleanup();return 1;}
侦听套接字
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) { //SOMAXCONN 最大连接数printf( "Listen failed with error: %ld\n", WSAGetLastError() );freeaddrinfo(result);closesocket(ListenSocket);WSACleanup();return 1;}
接受连接
创建ClientSocket 临时socket对象,接受客户端的连接
有几种不同的编程技术使用 Winsock,可用于侦听多个客户端连接。 一种编程方法是创建一个连续循环,该循环使用 侦听 函数检查连接请求 (请参阅 在套接字) 上进行侦听 。 如果出现连接请求,应用程序将调用 accept、 AcceptEx或 WSAAccept 函数,并将工作传递到另一个线程来处理请求。
SOCKET ClientSocket;ClientSocket = INVALID_SOCKET;CleintSocket = accept(ListenSocket,NULL,NULL);if(ClientSocket == INVALID_SOCKET){printf("accept failed %d\n",WSAGetLastError());WSACleanup();return 1;}
当客户端连接被接受后,服务器应用程序通常会将接受的客户端套接字传递 (上述示例代码中的 ClientSocket 变量) 到工作线程或 i/o 完成端口,并继续接受其他连接。
还有许多其他编程技术可用于侦听和接受多个连接。 其中包括使用 select 函数或 WSAPoll 函数。
收发数据
#define DEFAULT_BUFLEN 512char recvbuf[DEFAULT_BUFLEN];int iResult, iSendResult;int recvbuflen = DEFAULT_BUFLEN;// Receive until the peer shuts down the connectiondo {iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);if (iResult > 0) {printf("Bytes received: %d\n", iResult);// Echo the buffer back to the senderiSendResult = send(ClientSocket, recvbuf, iResult, 0);if (iSendResult == SOCKET_ERROR) {printf("send failed: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}printf("Bytes sent: %d\n", iSendResult);} else if (iResult == 0)printf("Connection closing...\n");else {printf("recv failed: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}} while (iResult > 0);
send 和 recv函数都分别返回发送或接收的字节数的整数值或错误。 每个函数也采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及使用的任何标志。
关闭套接字
当服务器将数据发送到客户端时,可以调用 shutdown 函数以指定 SD _ SEND 来关闭套接字的发送端。 这允许客户端释放此套接字的某些资源。 服务器应用程序仍然可以在套接字上接收数据。
// shutdown the send half of the connection since no more data will be sentiResult = shutdown(ClientSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}
完整代码
#undef UNICODE#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdlib.h>#include <stdio.h>// Need to link with Ws2_32.lib#pragma comment (lib, "Ws2_32.lib")// #pragma comment (lib, "Mswsock.lib")#define DEFAULT_BUFLEN 512#define DEFAULT_PORT "27015"int __cdecl main(void){WSADATA wsaData;int iResult;SOCKET ListenSocket = INVALID_SOCKET;SOCKET ClientSocket = INVALID_SOCKET;struct addrinfo *result = NULL;struct addrinfo hints;int iSendResult;char recvbuf[DEFAULT_BUFLEN];int recvbuflen = DEFAULT_BUFLEN;// Initialize WinsockiResult = WSAStartup(MAKEWORD(2,2), &wsaData);if (iResult != 0) {printf("WSAStartup failed with error: %d\n", iResult);return 1;}ZeroMemory(&hints, sizeof(hints));hints.ai_family = AF_INET;hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;hints.ai_flags = AI_PASSIVE;// Resolve the server address and portiResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);if ( iResult != 0 ) {printf("getaddrinfo failed with error: %d\n", iResult);WSACleanup();return 1;}// Create a SOCKET for connecting to serverListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);if (ListenSocket == INVALID_SOCKET) {printf("socket failed with error: %ld\n", WSAGetLastError());freeaddrinfo(result);WSACleanup();return 1;}// Setup the TCP listening socketiResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);if (iResult == SOCKET_ERROR) {printf("bind failed with error: %d\n", WSAGetLastError());freeaddrinfo(result);closesocket(ListenSocket);WSACleanup();return 1;}freeaddrinfo(result);iResult = listen(ListenSocket, SOMAXCONN);if (iResult == SOCKET_ERROR) {printf("listen failed with error: %d\n", WSAGetLastError());closesocket(ListenSocket);WSACleanup();return 1;}// Accept a client socketClientSocket = accept(ListenSocket, NULL, NULL);if (ClientSocket == INVALID_SOCKET) {printf("accept failed with error: %d\n", WSAGetLastError());closesocket(ListenSocket);WSACleanup();return 1;}// No longer need server socketclosesocket(ListenSocket);// Receive until the peer shuts down the connectiondo {iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);if (iResult > 0) {printf("Bytes received: %d\n", iResult);// Echo the buffer back to the senderiSendResult = send( ClientSocket, recvbuf, iResult, 0 );if (iSendResult == SOCKET_ERROR) {printf("send failed with error: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}printf("Bytes sent: %d\n", iSendResult);}else if (iResult == 0)printf("Connection closing...\n");else {printf("recv failed with error: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}} while (iResult > 0);// shutdown the connection since we're doneiResult = shutdown(ClientSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed with error: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}// cleanupclosesocket(ClientSocket);WSACleanup();return 0;}
client
创建socket
声明一个包含 sockaddr结构的 addrinfo对象并初始化这些值
struct addrinfo *result = NULL,*ptr = NULL, hints;ZeroMemory(&hints,sizeof(hints));hints.ai_family = AF_UNSPEC;hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;
调用 getaddrinfo 函数,请求在命令行上传递的服务器名称的 IP 地址。 在此示例中,客户端将连接到的服务器上的 TCP 端口被默认 _ 端口定义为27015。 Getaddrinfo 函数以检查是否有错误的整数的形式返回其值
#define DEFAULT_PORT "27015"// Resolve the server address and portiResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);if (iResult != 0) {printf("getaddrinfo failed: %d\n", iResult);WSACleanup();return 1;}
创建ConnectSocket
// Attempt to connect to the first address returned by// the call to getaddrinfoptr=result;// Create a SOCKET for connecting to serverConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,ptr->ai_protocol);
检查错误
if (ConnectSocket == INVALID_SOCKET) {printf("Error at socket(): %ld\n", WSAGetLastError());freeaddrinfo(result);WSACleanup();return 1;}
连接socket
调用 connect 函数,将创建的套接字和 sockaddr 结构作为参数传递。 检查常规错误。
// Connect to server.iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);if (iResult == SOCKET_ERROR) {closesocket(ConnectSocket);ConnectSocket = INVALID_SOCKET;}// Should really try the next address returned by getaddrinfo// if the connect call failed// But for this simple example we just free the resources// returned by getaddrinfo and print an error messagefreeaddrinfo(result);if (ConnectSocket == INVALID_SOCKET) {printf("Unable to connect to server!\n");WSACleanup();return 1;}
getaddrinfo函数用于确定 sockaddr 结构中的值。 本示例使用 getaddrinfo 函数返回的第一个 IP 地址来指定 传递给连接的 sockaddr 结构。 如果 第 一个 IP 地址的连接调用失败,请尝试从 getaddrinfo 函数返回的链接列表中的下一个 addrinfo结构
收发数据
#define DEFAULT_BUFLEN 512int recvbuflen = DEFAULT_BUFLEN;const char *sendbuf = "this is a test";char recvbuf[DEFAULT_BUFLEN];int iResult;// Send an initial bufferiResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);if (iResult == SOCKET_ERROR) {printf("send failed: %d\n", WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}printf("Bytes Sent: %ld\n", iResult);// shutdown the connection for sending since no more data will be sent// the client can still use the ConnectSocket for receiving dataiResult = shutdown(ConnectSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed: %d\n", WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}// Receive data until the server closes the connectiondo {iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);if (iResult > 0)printf("Bytes received: %d\n", iResult);else if (iResult == 0)printf("Connection closed\n");elseprintf("recv failed: %d\n", WSAGetLastError());} while (iResult > 0);
send 和 recv函数都分别返回发送或接收的字节数的整数值或错误。 每个函数也采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及使用的任何标志。
断开连接
客户端完成向服务器发送数据后,可以调用 shutdown 函数,指定 SD SEND 以关闭套接字 _ 的发送端。 这允许服务器释放此套接字的一些资源。 客户端应用程序仍可接收套接字上的数据
// shutdown the send half of the connection since no more data will be sentiResult = shutdown(ConnectSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed: %d\n", WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}
客户端应用程序收到数据后,将调用 closesocket 函数来关闭套接字。
使用套接字 DLL 完成客户端Windows,将调用 WSACleanup函数以释放资源
// cleanupclosesocket(ConnectSocket);WSACleanup();return 0;
完整代码
#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdlib.h>#include <stdio.h>// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib#pragma comment (lib, "Ws2_32.lib")#pragma comment (lib, "Mswsock.lib")#pragma comment (lib, "AdvApi32.lib")#define DEFAULT_BUFLEN 512#define DEFAULT_PORT "27015"int __cdecl main(int argc, char **argv){WSADATA wsaData;SOCKET ConnectSocket = INVALID_SOCKET;struct addrinfo *result = NULL,*ptr = NULL,hints;const char *sendbuf = "this is a test";char recvbuf[DEFAULT_BUFLEN];int iResult;int recvbuflen = DEFAULT_BUFLEN;// Validate the parametersif (argc != 2) {printf("usage: %s server-name\n", argv[0]);return 1;}// Initialize WinsockiResult = WSAStartup(MAKEWORD(2,2), &wsaData);if (iResult != 0) {printf("WSAStartup failed with error: %d\n", iResult);return 1;}ZeroMemory( &hints, sizeof(hints) );hints.ai_family = AF_UNSPEC;hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;// Resolve the server address and portiResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);if ( iResult != 0 ) {printf("getaddrinfo failed with error: %d\n", iResult);WSACleanup();return 1;}// Attempt to connect to an address until one succeedsfor(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {// Create a SOCKET for connecting to serverConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,ptr->ai_protocol);if (ConnectSocket == INVALID_SOCKET) {printf("socket failed with error: %ld\n", WSAGetLastError());WSACleanup();return 1;}// Connect to server.iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);if (iResult == SOCKET_ERROR) {closesocket(ConnectSocket);ConnectSocket = INVALID_SOCKET;continue;}break;}freeaddrinfo(result);if (ConnectSocket == INVALID_SOCKET) {printf("Unable to connect to server!\n");WSACleanup();return 1;}// Send an initial bufferiResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );if (iResult == SOCKET_ERROR) {printf("send failed with error: %d\n", WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}printf("Bytes Sent: %ld\n", iResult);// shutdown the connection since no more data will be sentiResult = shutdown(ConnectSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed with error: %d\n", WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return 1;}// Receive until the peer closes the connectiondo {iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);if ( iResult > 0 )printf("Bytes received: %d\n", iResult);else if ( iResult == 0 )printf("Connection closed\n");elseprintf("recv failed with error: %d\n", WSAGetLastError());} while( iResult > 0 );// cleanupclosesocket(ConnectSocket);WSACleanup();return 0;}
udp
server
#include <winsock2.h>#include<iostream>#pragma comment(lib,"ws2_32.lib")using namespace std;int main(){WORD wVersion;WSADATA wsaData;int er;//1.初始化版本信息wVersion = MAKEWORD(1, 1);//加载套接字库er = WSAStartup(wVersion, &wsaData);if (er != 0){return -1;}//检测套接字版本信息if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1){WSACleanup();return -2;}//2.创建服务器端套接字SOCKET sockSer = socket(AF_INET, SOCK_DGRAM, 0);SOCKADDR_IN addr_in;addr_in.sin_family = AF_INET;//地址族addr_in.sin_port = htons(7000);//端口addr_in.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//IPbind(sockSer, (sockaddr*)&addr_in, sizeof(addr_in));SOCKADDR_IN client_addr;int len = sizeof(sockaddr_in);char recvBuf[1024];recvfrom(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&client_addr, &len);cout << "接受到的数据:" << recvBuf << endl;//向客户端发送数据sendto(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&client_addr, len);closesocket(sockSer);WSACleanup();system("pause");return 0;}
client
#include "stdafx.h"#include <winsock2.h>#include<iostream>#pragma comment(lib,"ws2_32.lib")using namespace std;int main(){WORD wVersion;WSADATA wsaData;int er;//1.初始化版本信息wVersion = MAKEWORD(1, 1);//加载套接字库er = WSAStartup(wVersion, &wsaData);if (er != 0){return -1;}//检测套接字版本信息if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1){WSACleanup();return -2;}//2.创建服务器端套接字SOCKET sockSer = socket(AF_INET, SOCK_DGRAM, 0);SOCKADDR_IN addr_in;addr_in.sin_family = AF_INET;//地址族addr_in.sin_port = htons(7000);//端口addr_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");bind(sockSer, (sockaddr*)&addr_in, sizeof(addr_in));SOCKADDR_IN addr;int len = sizeof(addr);//向服务器发送数据char sendBuf[1024];sendto(sockSer, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&addr, len);//接受数据char recvBuf[1024];recvfrom(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addr, &len);cout << "接受到服务器端的数据:" << recvBuf << endl;closesocket();WSACleanup();system("pause");return 0;}
