基于 TCP 的 socket 编程
/*
服务器端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.将我们创建的套接字,绑定到本机地址的某一端口上 bind
4.为套接字设置监听模式,准备客户请求 listen
5.等待客户请求到来。当请求到来,将接受连接请求,并返回一个新的对应于此次连接的套接字 accept
6.用新返回的套接字和客户端进行通信 send / recv
7.在通信结束后,关闭套接字 closesocket
客户端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.向服务器发出请求连接 connect
4.和服务器进行通信 send / recv
5.在通信结束后,关闭套接字 closesocket
*/
服务器端代码:
#include <Winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") void main() { // 加载套接字库,并进行套接字的版本协商 WORD wVersionRequested; // 指定将要加载的 winsock 库版本 WSADATA wsaData; // 用于存储加载的 winsock 库版本信息 int result; // 用于检测 WSAStartup 函数运行结果 wVersionRequested = MAKEWORD(1, 1); // 设定版本 result = WSAStartup(wVersionRequested, &wsaData); // 函数 WSAStartup 调用成功返回 0 // 出错处理 if (result != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return; } // 创建套接字 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); // 绑定套接字 SOCKADDR_IN addrInfo; // 存储本地主机地址信息 addrInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本地主机地址 addrInfo.sin_port = htons(6000); // 端口号 addrInfo.sin_family = AF_INET; // 地址族 bind(sock, (SOCKADDR *)&addrInfo, sizeof(SOCKADDR)); // 设置套接字监听模式 listen(sock, 5); SOCKADDR_IN addrInfoClient; // 存储客户端地址信息 int len = sizeof(SOCKADDR); while (true) { // 等待客户请求到来,并返回用于通信的套接字 SOCKET sockConnect = accept(sock, (SOCKADDR *)&addrInfoClient, &len); // 下面通过刚建立的套接字,来进行通信 // 发送数据 char sendBuf[100]; sprintf(sendBuf, "这是服务器端,主机地址:%s", inet_ntoa(addrInfo.sin_addr)); send(sockConnect, sendBuf, strlen(sendBuf), 0); // 接收数据 char recvBuf[100]; recv(sockConnect, recvBuf, strlen(recvBuf), 0); // 打印接收的数据 printf("%s\n", recvBuf); closesocket(sockConnect); } }
客户端代码:
#include <Winsock2.h> #include <stdio.h> #pragma comment(lib,"Ws2_32.lib") void main() { // 加载套接字库,并进行套接字的版本协商 WORD wVersionRequested; // 指定将要加载的 winsock 库版本 WSADATA wsaData; // 用于存储加载的 winsock 库版本信息 int result; // 用于检测 WSAStartup 函数运行结果 wVersionRequested = MAKEWORD(1, 1); // 设定版本 result = WSAStartup(wVersionRequested, &wsaData); // 函数 WSAStartup 调用成功返回 0 // 出错处理 if (result != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return; } // 创建套接字 SOCKET sockConnect = socket(AF_INET, SOCK_STREAM, 0); // 向服务器发出连接请求 SOCKADDR_IN addrInfoServer; // 存储服务器端地址信息 addrInfoServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrInfoServer.sin_port = htons(6000); addrInfoServer.sin_family = AF_INET; // 向服务器发出连接请求 connect(sockConnect, (SOCKADDR *)&addrInfoServer, sizeof(SOCKADDR)); // 接收数据 char recvBuf[100]; recv(sockConnect, recvBuf, sizeof(recvBuf), 0); printf("%s\n", recvBuf); // 发送数据 char sendBuf[100] = "这是客户端\n"; send(sockConnect, sendBuf, sizeof(sendBuf) + 1, 0); //关闭套接字 closesocket(sockConnect); WSACleanup(); system("pause"); return; }
================================================
基于 UDP 无连接的 socket 编程
/*
服务端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.将创建的套接字绑定到一个本地地址和端口上 bind
4.等待接收数据。后与客户端实现实时交流 recvfrom / sendto
5.关闭套接字 closesocket
客户端程序流程:
1.加载套接字库 WSAStartup
2.创建套接字 socket
3.向服务器发送数据.后与服务端实现实时交流 recvfrom / sendto
4.关闭套接字 closesocket
*/
服务器端代码:
#include <Winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") void main() { // 加载套接字库,并进行套接字的版本协商 WORD wVersionRequested; // 指定将要加载的 winsock 库版本 WSADATA wsaData; // 用于存储加载的 wdnsock 库版本信息 int result; // 用于检测 WSAStartup 函数运行结果 wVersionRequested = MAKEWORD(1, 1); // 设定版本 result = WSAStartup(wVersionRequested, &wsaData); // 函数 WSAStartup 调用成功返回 0 // 出错处理 if (result != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return; } // 创建用于套接字 SOCKET sockConnect = socket(AF_INET, SOCK_DGRAM, 0); // 绑定套接字 SOCKADDR_IN addrInfo; // 存储本地主机地址信息 addrInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本地主机地址 addrInfo.sin_port = htons(6000); // 端口号 addrInfo.sin_family = AF_INET; // 地址族 bind(sockConnect, (SOCKADDR *)&addrInfo, sizeof(SOCKADDR)); // 等待接收数据 char recvBuf[100]; // 接收数据缓冲 char sendBuf[100]; // 发送数据缓冲 char tempBuf[200]; SOCKADDR_IN addrInfoClient; // 存储客户端地址信息 int len = sizeof(SOCKADDR); while (true) { recvfrom(sockConnect, recvBuf, strlen(recvBuf), 0, (SOCKADDR *)&addrInfoClient, &len); if ('q' == recvBuf[0]) { sendto(sockConnect, "q", strlen("q") + 1, 0, (SOCKADDR *)&addrInfoClient, len); printf("聊天结束"); break; } sprintf(tempBuf, "%s 说:%s", inet_ntoa(addrInfoClient.sin_addr), recvBuf); printf("%s\n", tempBuf); // 发送数据 printf("我说:"); gets(sendBuf); sendto(sockConnect, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrInfoClient, len); } // 关闭套接字 closesocket(sockConnect); WSACleanup(); }
客户端代码:
#include <Winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") void main() { // 加载套接字库,并进行套接字的版本协商 WORD wVersionRequested; // 指定将要加载的 winsock 库版本 WSADATA wsaData; // 用于存储加载的 wdnsock 库版本信息 int result; // 用于检测 WSAStartup 函数运行结果 wVersionRequested = MAKEWORD(1, 1); // 设定版本 result = WSAStartup(wVersionRequested, &wsaData); // 函数 WSAStartup 调用成功返回 0 // 出错处理 if (result != 0) { return; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return; } // 创建套接字 SOCKET sockConnect = socket(AF_INET, SOCK_DGRAM, 0); // 向服务器发送数据 SOCKADDR_IN addrInfoServer; // 存储服务器地址信息 addrInfoServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 指定服务器地址 addrInfoServer.sin_port = htons(6000); // 端口号 addrInfoServer.sin_family = AF_INET; // 地址族 int len = sizeof(SOCKADDR); char recvBuf[100]; // 接收数据缓冲 char sendBuf[100]; // 发送数据缓冲 char tempBuf[200]; while (true) { // 发送数据 printf("我说:"); gets(sendBuf); sendto(sockConnect, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrInfoServer, len); // 等待并接收数据 recvfrom(sockConnect,recvBuf, strlen(recvBuf), 0, (SOCKADDR*)&addrInfoServer, &len); if ('q' == recvBuf[0]) { sendto(sockConnect, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrInfoServer, len); printf("聊天结束"); break; } sprintf(tempBuf, "%s 说:%s", inet_ntoa(addrInfoServer.sin_addr), recvBuf); printf("%s\n", tempBuf); } // 关闭套接字 closesocket(sockConnect); WSACleanup(); }
vc网络编程常用类型解析: 1. SOCKET 类型 SOCKET 是 socket 套接字类型,在 WINSOCK2.H 中有如下定义: typedef unsigned u_int; typedef u_int SOCKET; 可知套接字实际上就是一个无符号整形,它将被 Socket 环境管理和使用。 套接字将被创建、设置、用来发送和接收数据,最后会被关闭。 2.WORD 类型、MAKEWORD、LOBYTE、HIBYTE 宏 WORD 类型是一个 16 位的无符号整型, 在 WTYPES.H 中被定义为: typedef unsigned short WORD; 其目的是提供两个字节的存储, 在 Socket 中这两个字节可以表示主版本号和副版本号。 使用 MAKEWORD 宏可以给一个 WORD 类型赋值。例如要表示主版本号 2, 副版本号 0,可以使用如下代码: WORD wVersionRequested; wVersionRequested = MAKEWORD(2, 0); 注意低位内存存储主版本号 2, 高位内存存储副版本号 0,其值为 0x0002。 使用宏 LOBYTE 可以读取 WORD 的低位字节, HIBYTE 可以读取高位字节。 3.WSADATA 类型和 LPWSADATA 类型 WSADATA 类型是一个结构,描述了 Socket 库的一些相关信息,其结构定义如下: typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN + 1]; char szSystemStatus[WSASYS_STATUS_LEN + 1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR* lpVendorInfo; }WSADATA; typedef WSADATA FAR* LPWSADATA; 值得注意的是 wVersion 字段,存储了 Socket 的版本类型。LPWSADATA 是 WSADATA 的指针类型。 他们通过 Socket 的初始化函数 WSAStartup 读取出来。 vc网络编程常用函数解析: 1. WSAStartup 函数 用于初始化 Socket 环境,函数原型: int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); 其返回值为整型,调用方式为 PASCAL (即标准类型,PASCAL 等于__stdcall),参数有两个, 第一个参数为 WORD 类型,指明了 Socket 的版本号,第二个参数为 LPWSADATA,指向一个用于存储 Socket 库信息的WSAStartup结构。 返回值: 返回值为0,则初始化成功,若不为0则为失败。 2.WSACleanup 函数 这是 Socket 环境的退出函数,函数原型: int WSACleanup (void); 返回值: 返回值为0表示成功,SOCKET_ERROR 表示失败。 3.socket 函数 socket 套接字的创建函数,函数原型: SOCKET socket(int af, int type, int protocol ); 第一个参数为:int af, 代表网络地址族,目前只有一种取值有效,即 AF_INET, 代表 internet 地址族; 第二个参数为:int type, 代表网络协议类型, SOCK_DGRAM 代表 UDP 协议, SOCK_STREAM 代表 TCP 协议。 第三个参数为:int protocol,指定网络地址族特殊协议,目前无用,赋值0即可。 返回值: 返回值为 SOCKET, 若返回INVALID_SOCKET 则失败。 4.bind 函数 用于将套接字绑定到一个已知地址上,函数原型: int bind(SOCKET s, const struct sockaddr FAR *name, int namelen); 第一个参数为:SOCKET s, 指定将被绑定的套接字。 第二个参数为:SOCKADDR_IN *name, 是一个sockaddr结构指针,该结构中包含了要绑定的地址和端口。 第三个参数为:int namelen, 确定第二个参数的结构长度。 返回值: 成功返回0,失败返回SOCKET_ERROR。 下面对其涉及的类型作一番解析: sockaddr 类型: sockaddr 类型是用来表示 Socket 地址的类型,同上面的 socketaddr_in 类型相比,sockaddr 的适用范围更广, 因为sockeaddr_in只适用于 TCP/IP 地址。sockaddr 的定义如下: struct sockaddr { ushort sa_family; char sa_data[14]; }; 可知sockaddr 的16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockadddr的。 事实上也往往使用这种方法。 sockaddr_in 定义了socket发送和接收数据包的地址,其定义如下: strucr sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; 其中 in_addr 定义如下: struct in_addr { union { struct {u_char s_b1, s_b2, s_b3, s_b4} S_un_b; struct {u_short s_w1, s_w2} S_un_w; u_long S_addr; }S_un; }; 首先阐述 in_addr 的信义。 很显然它是一个存储 ip 地址的联合体,有三种表达方式: 第一种用四个字节来表示IP地址的四个数字; 第二种用两个双字节来表示IP地址; 第三种用一个长整型来表示IP地址; 给 in_addr 赋值的一种最简单方法是使用 inet_addr 函数, 它可以把一个代表IP地址的字符串赋值 转换为in_addr类型。如: addrServer.sin_addr = inet_addr("192.168.0.2"); 其反函数是 inet_ntoa,可以把一个 in_addr 类型转换为一个字符串。 sockaddr_in的含义比in_addr的含义要广泛,其各个字段的含义和取值如下: 第一字段 short sin_family,代表网络地址族,如前所述,只能取值AF_INET; 第二字段 u_short sin_port, 代表IP地址端口,由程序员指定; 第三字段 struct in_addr sin_addr, 代表IP地址; 第四个字段char sin_zero[8],是为了保证sockaddr_in与SOCKADDR类型的长度相等而填充进来的字段。 5.listen 函数 该函数让一个套接字在指定IP地址的指定端口处监听连接请求的到来,函数原型: int listen( SOCKET s, int backlog ); 该函数使得一个进程可以接受其他进程的请求,从而成为一个服务器进程。 在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。 listen 函数一般在调用bind之后、调用accept之前调用。 返回值: 成功则返回0,失败返回SOCKET_ERROR,可以调用函数WSAGetLastError来取得错误代码。 6.accept函数 该函数从连接请求队列中获得连接信息,并创建新的套接字用于收发数据,实现服务器与客户端的通信。函数原型: SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen); 第一个参数:SOCKET s, 监听套接字 第二个参数:struct sockaddr addr, 存储请求连接的客户端IP地址、端口信息 第三个参数:int addrlen,第二个参数所占空间大小 返回值: 成功返回新套接字,失败返回错误信息 7.connect 函数 向指定的网络主机请求连接,函数原型: int connect(SOCKET s, const struct sockaddr FAR *name, int namelen); 第一个参数:SOCKET s, 客户端用于收发数据的套接字。 第二个参数:struct sockaddr *name, 指定网络主机IP地址和端口号。 第三个参数:int namelen, 第二参数长度 返回值: 成功返回0,失败返回-1。 8.sendto、recvfrom、send、recv函数 在 Socket 中有两套发送和接收函数。一是sendto 和recvfrom; 二是send 和 recv。 前一套在函数参数中要指明地址(UDP协议), 而后一套需要先将套接字和一个地址绑定,然后直接发送和接收,不需绑定地址。 函数原型: int sendto( SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen); int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen); int send(SOCKET s,const char FAR *buf, int len, int flags); int recv(SOCKET s, char FAR *buf, int len, int flags); 第一个参数: 套接字 第二个参数: 数据指针 第三个参数: 数据长度 第四个参数: 收发数据方式的标识,如果不需要特殊要求可以设置为0,其他值可参考MSDN; 第五个参数: 目标主机地址 第六个参数: 地址的长度 返回值: 运行成功则返回收发数据的字节数,失败返回SOCKET_ERROR 9.closesocket 函数 关闭套接字,函数原型: int closesocket( SOCKET s ); 返回值: 成功返回0,失败返回SOCKET_ERROR。