一.概述:

本次练习的是TCP/UDP套接字编程,使用的是winsocket,对主要的库函数进行简绍,并实现了一个程序:实现服务器与客户端之间的通信,在服务器端实现记录用户名和密码,客服端可以实现用户名和密码的输入和查找,并且检查是否匹配。(参考  <<Visual C++网络编程>>)

PS: 127.0.0.1是回路地址,用于在同一台机器上测试代码。端口号要大于1024。



二.基于TCP/UDP协议的套接字编程详解:

基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:

典型的UDP客户/服务器程序函数调用图:



三.相关数据结构:

(1).sockaddr:sockaddr用来保存一个套接字

struct sockaddr{    unsigned short int sa_family; //指定通信地址类型,如果是TCP/IP通信,则值为AF_inet    char sa_data[14]; //最多用14个字符长度,用来保存IP地址和端口信息}; }

(2).

sockaddr_in的功能与socdaddr相同,也是用来保存一个套接字的信息,不同的是将IP地址与端口分开为不同的成员,定义如下:

struct sockaddr_in{    unsigned short int sin_family; //指定通信地址类型    uint16_t sin_port; //套接字使用的端口号    struct in_addr sin_addr; //需要访问的IP地址    unsigned char sin_zero[8]; //未使用的字段,填充为0}; }

在这一结构中,in_addr也是一个结构体,定义如下,用于保存一个IP地址:

struct in_addr{    uint32_t  s_addr;};

(3).WSAData:包含Winsock库的版本信息,这个结构是在调用函数WSAStartup时由系统填入。

struct WSAData { 

WORD wVersion; 
WORD wHighVersion; 
char szDescription[WSADESCRIPTION_LEN+1]; 
char szSystemStatus[WSASYSSTATUS_LEN+1]; 
unsigned short iMaxSockets; 
unsigned short iMaxUdpDg; 
char FAR * lpVendorInfo; 
}; 
    wVersion为你将使用的Winsock版本号,

    wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。

    szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。
    iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。
    iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。

(4).SOCKET:即套接字句柄,为一个32位的整数。

typedef unsigned int SOCKET



四.Winsock相关函数:

(1).WSAStartup函数:初始化Winsock

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)

 wVersionRequested参数:是一个WORD(双字节)数值,它指定了应用程序需要使用的Winsock版本.

    主版本号在 低字节, 次版本号在 高字节。

    实例:希望版本号为 1.2 可以如下代码:

    wVersionRequested = 0x0201。

    lpWSAData参数:指向WSADATA数据结构的指针,该结构用于返回本机的Winsock系统实现的信息.

    该结构WhighVersion和wVersion两个域系统支持的最高版本,后者是系统希望调用者使用的版本.

 函数成功 返回0; 否则返回错误码.

一般可以这样初始化:

WSADATA wsaData;

WSAStartup(0x0202, &wsaData); 

(2).WSACleanup函数:Winsock程序在退出之前都必须要调用WSAClernup,以便系统可以释放资源。

int WSACleanup(void)

(3).WSAGetLastError函数:当一个Winsock函数返回一个失败值时,调用这个函数可以获取具体的失败原因。

int WSAGetLastError(void)

(4).inet_addr函数:地址转换函数

ussigned long inet_addr(const char FAR* cp)

例如:inet_addr("127.0.0.1")

(5).字节序转换函数:

u_long htonl(u_long hostlong);

u_short htons(u_short hostshort);

u_long ntonl(u_long netlong);

u_short ntons(u_short netshort);

htonl和htons用于把主机号字节序转换为网络字节序,ntonl和ntons则相反。(host:主机)



五.与实现通信相关的函数:

(1).sock函数:返回一个 套接字句柄。

int socket(int family, int type, int protocol);   

  1.  * 说明:  

  2.  * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;  

  3.  * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:  

  4.  * (1)AF_INET         IPv4因特网域  

  5.  * (2)AF_INET6        IPv6因特网域  

  6.  * (3)AF_UNIX         Unix域  

  7.  * (4)AF_ROUTE        路由套接字  

  8.  * (5)AF_KEY          密钥套接字  

  9.  * (6)AF_UNSPEC       未指定  

  10.  *  

  11.  * type确定socket的类型,常用类型如下:  

  12.  * (1)SOCK_STREAM     有序、可靠、双向的面向连接字节流套接字  

  13.  * (2)SOCK_DGRAM      长度固定的、无连接的不可靠数据报套接字  

  14.  * (3)SOCK_RAW        原始套接字  

  15.  * (4)SOCK_SEQPACKET  长度固定、有序、可靠的面向连接的有序分组套接字  

  16.  *  

  17.  * protocol指定协议,常用取值如下:  

  18.  * (1)0               选择type类型对应的默认协议  

  19.  * (2)IPPROTO_TCP     TCP传输协议  

  20.  * (3)IPPROTO_UDP     UDP传输协议  

  21.  * (4)IPPROTO_SCTP    SCTP传输协议  

  22.  * (5)IPPROTO_TIPC    TIPC传输协议  

(2).closesocket函数:关闭一个套接字。

int closesocket(SOCKET s);

传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。

(3).shutdown函数:停止 Socket 接收/传送的功能

int shutdown(SOCKET s,int how)

参数: s :Socket 的识别码,

          how :代表该停止那些动作的标帜 

传回值: 成功返回 0 ,失败 返回 SOCKET_ERROR 。
若 how 的值为 0,则不再接收资料。 
若 how 的值为 1,则不再允许传送资料。 
若 how 的值为 2,则不再接收且不再传送资料。 
注意:shutdown() 函式并没有将 Socket 关闭,所以该 Socket 所占用之资源必须在呼叫closesocket() 之后才会释放。 

(4).bind函数:把相关套接字句柄绑定到addr地址,绑定之后客户端/服务器就可以通过该地址连接到服务器/客户端。

int bind(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);   

   s参数: 为一个套接字句柄;  

   addr参数:是一个指向特定协议地址结构的指针;  

   addrlen参数:是地址结构的长度;  

传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。

PS:当addr结构体中的sin_addr.s_addr为INADDR_ANY时,表示绑定到通配地址(即没有指定的mac地址),这时可以接受来自所有网络接口的连接请求,适合于有多个网卡的情况。

(5).listen函数:设定 Socket 为监听状态,准备被连接。

int listen(SOCKET s,int backlog)

参 数: s Socket 的识别码,

              backlog 未真正完成连接前彼端的连接要求的最大个数 (连接队列中的最大数)
传回值: 成功 返回0 , 失败 返回 SOCKET_ERROR 。

(6).accept函数:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠。

   int accept(SOCKER s, struct sockaddr FAR* addr, int FAR* addrlen)     

参数 s:已经绑定并进入监听状态的套接字句柄。

   addr:用于保存客户端的地址信息。

   addrlen:用于保存客户端的地址信息的结构体大小

PS:若没有连接请求等待处理,accept会阻塞直到一个请求到来;  

返回值:若成功返回套接字描述符,出错返回INVALID_SOCKET;如果成功,此时的套接字为已连接套接字,后面的通信是用这个已连接套接字来描述的。

(7).recv函数:用于TCP流式套接字编程中接受来自客户端的消息。

int recv(SOCKET s, char FAR* buf, int len, int flags)

参数:s:accept返回的一个已连接套接字

    buf:用于接受数据的缓存区

    len:buf的长度,以字节问单位。

    flags:用来影响recv的行为,一般为0;

返回值:成功时,返回实际接收的字节数,失败返回SOCKET_ERROR。

PS:recv为阻塞式的等待有数据可接收。

(8).send函数:用于TCP流式套接字编程中发送消息到客户端。

int send(SOCKET s, char FAR* buf, int len, int flags)

参数:s:accept返回的一个已连接套接字

    buf:用于发送数据的缓存区

    len:buf的长度,以字节问单位。

    flags:用来影响recv的行为,一般为0;

返回值:成功时,返回实际发送的字节数,失败返回SOCKET_ERROR。

(9).connect函数:连接服务器地址,使s成为一个已连接套接字。

int connect(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);   

   s参数: 为一个套接字句柄;  

   addr参数:是一个指向特定协议地址结构的指针;  

   addrlen参数:是地址结构的长度;  

传回值: 成功 返回0 ,此时代表已经和服务器连接成功,s成为一个已连接套接字; 失败 返回 SOCKET_ERROR 。

(10).recvfrom函数用于UDP报文套接字编程中接受来自地址为from的主机消息。

int recvfrom(SOCKET s, char FAR* buf, int len, int flags,

         struct socket FAR* from, int FAR* fromlen)

参数:s为一个已连接套接字。

    buf:数据接收缓冲区。

    len:数据接收缓存区的大小。

    flags:用于控制recvfrom行为的一些标志,一般为0.

    from:对方的套接字地址。

    fromlen:对方的套接字地址结构的大小。

返回值:成功返回实际接受的字节数,失败返回SOCKET_ERROR;如果buf大小不足以容纳接收到的数据报,那么返回一个WSAEMSGSIZE错误。

PS:UDP套接字编程中的接收和发送信息都是以数据报为单位的。每次recvfrom都是接收一个独立的数据报,不同的recvfrom所接收的数据报之间没有任何关系(即不会出现一个recvfrom接收一个数据报的一部分,另一个接收这个数据报的另一部分的情况)。

(11)..sendto函数用于UDP报文套接字编程中发送到地址为from的主机消息。

int sendto(SOCKET s, char FAR* buf, int len, int flags,

         struct socket FAR* to, int FAR* tolen)

参数:s为一个已连接套接字。

    buf:发送数据的缓冲区。

    len:发送数据的缓存区大小。

    flags:用于控制sendto行为的一些标志,一般为0.

    to:对方的套接字地址。

    tolen:对方的套接字地址结构的大小。

返回值:成功返回实际发送的字节数,失败返回SOCKET_ERROR;要发送的字节数为len时,如果len大于UDP数据报的最大值,那么返回一个WSAEMSGSIZE错误。

PS:对于UDP而言,sendto的行为是全部或者没有(all or nothing),要么成功发送所有要求发送的字节,要么发送失败。



六.实际步骤参考上图。



七.实现功能的代码:

(1)TCP套接字编程:

服务器:

#include
#include
#include
#pragma comment(lib,"Ws2_32.lib")using namespace std;struct Data{ char* _usser; char* _possword; Data() :_usser(new char[1024]) , _possword(new char[1024]) {}};const int PORT = 2000;const int LEN = 1024;char buf[LEN];Data messeage[LEN];SOCKET sListen;sockaddr_in saListen; sockaddr_in saClient;SOCKET serverToClient;void Find()   //查找用户名 和 密码是否匹配存在{ char* msUsser = "请输入你要查找的用户名:"; send(serverToClient, msUsser, strlen(msUsser), 0); memset(buf, 0, LEN); int ret = recv(serverToClient, buf, LEN, 0);//接收用户名 for (size_t i = 0; i < LEN; i++) { if (strcmp(messeage[i]._usser,buf) == 0) { char* msPsw = "请输入所查找用户名的密码:"; send(serverToClient, msPsw, strlen(msPsw), 0); memset(buf, 0, LEN); int ret = recv(serverToClient, buf, LEN, 0);//接收密码 if (strcmp(messeage[i]._possword, buf) == 0)      //s注意用trcmp函数 { char* ms = "用户名和密码匹配存在\n请选择->"; send(serverToClient, ms, strlen(ms), 0); } else { char* mssage = "用户名正确 密码错误\n请选择->"; send(serverToClient, mssage, strlen(mssage), 0); } memset(buf, 0, LEN); recv(serverToClient, buf, LEN, 0); return; } } char* msUsserNot = "用户名不存在\n请选择->"; send(serverToClient, msUsserNot, strlen(msUsserNot), 0); memset(buf, 0, LEN); recv(serverToClient, buf, LEN, 0);}void Record(){ size_t index = 0; while (true) { while(buf[0] == '2')    //只有用户名和密码都输入完才能查找//      while { Find(); } if (buf[0] == '0')   //如果客服端输入0   则退出连接 { char* ms = "退出成功"; int len = strlen(ms); send(serverToClient, ms, len, 0); cout << "服务器退出连接!!!" << endl; return; } char* msUsser = "请输入用户名:"; int len = strlen(msUsser); send(serverToClient, msUsser, len, 0); memset(buf, 0, LEN); int ret = recv(serverToClient, buf, LEN, 0);//接收用户名 if (ret == SOCKET_ERROR)  //表示接收失败 { cout << "Client closed !!!" << endl; int size = sizeof(sockaddr); serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字  cout << "accept new" << endl; } strcpy_s(messeage[index]._usser,1024, buf);   //注意 用户名不用++ char* msPsw = "请输入密码:"; send(serverToClient, msPsw, strlen(msPsw), 0); memset(buf, 0, LEN); int retTwo = recv(serverToClient, buf, LEN, 0);//接收密码 if (retTwo == SOCKET_ERROR)  //表示接收失败 { cout << "Client closed !!!" << endl; int size = sizeof(sockaddr); serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字  cout << "accept new" << endl; } strcpy_s(messeage[index++]._possword,1024, buf); memset(buf, 0, LEN); char* choice = "请选择->"; send(serverToClient, choice, strlen(choice), 0); memset(buf, 0, LEN); recv(serverToClient, buf, LEN, 0);//接收客户的选择 }}int main(){ WSADATA wsaData; WSAStartup(0x0202, &wsaData);   //初始化winsock sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //创建一个套接字 //绑定分配的 port 和 ip地址 saListen.sin_family = AF_INET; saListen.sin_addr.s_addr = htonl(INADDR_ANY); saListen.sin_port = htons(PORT); bind(sListen, (sockaddr*)&saListen, sizeof(sockaddr)); listen(sListen, 5);      //进入监听状态 int size = sizeof(sockaddr);    serverToClient = accept(sListen, (sockaddr*)&saClient, &size);   //创建一个连接套接字 int ret = recv(serverToClient, buf, LEN, 0); if (ret == SOCKET_ERROR)  //表示接收失败 { cout << "Client closed !!!" << endl; int size = sizeof(sockaddr); serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字  cout << "accept new" << endl; } Record(); return 0;}


客户端:

//本次练习存在很多漏洞     //当相同的代码段重复出现多次时  变量的命名很重要//客户端只要接受和发送信息就可   关键协议在服务端实现//注意  send  与 recv  要匹配使用#include
#include
#pragma comment(lib,"WS2_32.lib")using namespace std;const int PORT = 2000;const int LEN = 1024;int main(){ WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET client; client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in saServer = {0}; saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = inet_addr("127.0.0.1"); saServer.sin_port = htons(PORT); int ret = connect(client, (sockaddr*)&saServer, sizeof(saServer)); if (ret == 0) { printf("connect access :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port); } else { printf("connect faild :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port); } cout << "............0:退出    1:输入    2:查找 ..............." << endl; char buf[LEN]; printf("请选择->  "); gets_s(buf); send(client, buf, strlen(buf), 0); while (true) { memset(buf, 0, LEN); int retUsser = recv(client, buf, LEN, 0); buf[retUsser] = '\0'; cout << buf << endl; gets_s(buf);    send(client, buf, strlen(buf), 0); } return 0;}

执行结果:


(2).UDP套接字编程:

服务器:

#include
#include
#include
#pragma comment(lib,"Ws2_32.lib")using namespace std;struct Data{ char* _usser; char* _possword; Data() :_usser(new char[1024]) , _possword(new char[1024]) {}};const int PORT = 2000;const int LEN = 1024;char buf[LEN];Data messeage[LEN];SOCKET sListen;           //服务器套接字sockaddr_in saListen;     //本地套接字地址sockaddr_in saClient;     //客户端套接字地址//SOCKET serverToClient;    //已连接套接字int clientSize = sizeof(saClient);       // ....改....void Find()   //查找用户名 和 密码是否匹配存在{ char* msUsser = "请输入你要查找的用户名:"; sendto(sListen, msUsser, strlen(msUsser), 0, (SOCKADDR*)&saClient, clientSize);    // ....改.... memset(buf, 0, LEN); int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收用户名    // ....改.... for (size_t i = 0; i < LEN; i++) { if (strcmp(messeage[i]._usser, buf) == 0) { char* msPsw = "请输入所查找用户名的密码:"; sendto(sListen, msPsw, strlen(msPsw), 0, (SOCKADDR*)&saClient, clientSize);      // ....改.... memset(buf, 0, LEN); int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收密码     // ....改.... if (strcmp(messeage[i]._possword, buf) == 0)      //s注意用trcmp函数 { char* ms = "用户名和密码匹配存在\n请选择->"; sendto(sListen, ms, strlen(ms), 0, (SOCKADDR*)&saClient, clientSize);    // ....改.... } else { char* mssage = "用户名正确 密码错误\n请选择->"; sendto(sListen, mssage, strlen(mssage), 0, (SOCKADDR*)&saClient, clientSize);    // ....改.... } memset(buf, 0, LEN); recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改.... return; } } char* msUsserNot = "用户名不存在\n请选择->"; sendto(sListen, msUsserNot, strlen(msUsserNot), 0, (SOCKADDR*)&saClient, clientSize);   // ....改.... memset(buf, 0, LEN); recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改....}void Record(){ size_t index = 0; while (true) { while (buf[0] == '2')    //只有用户名和密码都输入完才能查找//      while { Find(); } if (buf[0] == '0')   //如果客服端输入0   则退出连接 { char* ms = "退出成功"; int len = strlen(ms); sendto(sListen, ms, len, 0, (SOCKADDR*)&saClient, clientSize);    // ....改.... cout << "服务器退出连接!!!" << endl; return; } char* msUsser = "请输入用户名:"; int messageLen = strlen(msUsser); sendto(sListen, msUsser, messageLen, 0, (SOCKADDR*)&saClient, clientSize);  // ....改.... memset(buf, 0, LEN); int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收用户名    // ....改.... if (ret == SOCKET_ERROR)  //表示接收失败 { cout << "recvfrom error !!!" << endl;// int size = sizeof(sockaddr);                    // ....改.... // serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字   // ....改....// cout << "accept new" << endl;      // ....改.... } strcpy_s(messeage[index]._usser, 1024, buf);   //注意 用户名不用++ char* msPsw = "请输入密码:"; sendto(sListen, msPsw, strlen(msPsw), 0, (SOCKADDR*)&saClient, clientSize);      // ....改.... memset(buf, 0, LEN); int retTwo = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收密码       // ....改.... if (retTwo == SOCKET_ERROR)  //表示接收失败 { cout << "recvfrom error !!! !!!" << endl;// int size = sizeof(sockaddr);          // ....改....// serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字        // ....改....// cout << "accept new" << endl;          // ....改.... } strcpy_s(messeage[index++]._possword, 1024, buf); memset(buf, 0, LEN); char* choice = "请选择->"; sendto(sListen, choice, strlen(choice), 0, (SOCKADDR*)&saClient, clientSize);      // ....改.... memset(buf, 0, LEN); recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);//接收客户的选择    // ....改.... }}int main(){ WSADATA wsaData; WSAStartup(0x0202, &wsaData);   //初始化winsock sListen = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);  //创建一个套接字     ....改.... //绑定分配的 port 和 ip地址 saListen.sin_family = AF_INET; saListen.sin_addr.s_addr = htonl(INADDR_ANY); saListen.sin_port = htons(PORT); bind(sListen, (sockaddr*)&saListen, sizeof(sockaddr));// listen(sListen, 5);      //进入监听状态// int size = sizeof(sockaddr);// serverToClient = accept(sListen, (sockaddr*)&saClient, &size);   //创建一个连接套接字 int ret = recvfrom(sListen, buf, LEN, 0, (SOCKADDR*)&saClient, &clientSize);     // ....改.... if (ret == SOCKET_ERROR)  //表示接收失败 { cout << "recvfrom error !!!" << endl;// int size = sizeof(sockaddr);                     // ....改....// serverToClient = accept(sListen, (sockaddr*)&saClient, &size);  //重新创建一个连接套接字       // ....改....// cout << "accept new" << endl;                    // ....改.... } Record(); return 0;}


客户端:

//本次练习存在很多漏洞     //当相同的代码段重复出现多次时  变量的命名很重要//客户端只要接受和发送信息就可   关键协议在服务端实现//注意  send  与 recv  要匹配使用#include
#include
#pragma comment(lib,"WS2_32.lib")using namespace std;const int PORT = 2000;const int LEN = 1024;int main(){ WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET client; client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);      // ....改.... sockaddr_in saServer = { 0 }; saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = inet_addr("127.0.0.1"); saServer.sin_port = htons(PORT); int ret = connect(client, (sockaddr*)&saServer, sizeof(saServer)); if (ret == 0) { printf("connect access :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port); } else { printf("connect faild :%d:%d\n", saServer.sin_addr.s_addr, saServer.sin_port); } cout << "............0:退出    1:输入    2:查找 ..............." << endl; char buf[LEN]; int serverSize = sizeof(saServer);     // ....改.... printf("请选择->  "); gets_s(buf); int retlen = sendto(client, buf, strlen(buf), 0, (SOCKADDR*)&saServer, sizeof(saServer));   // ....改.... if (retlen == WSAEMSGSIZE)    // ....改.... { cout << "over size !" << endl; } else if (retlen == 0) { cout << "sendto error !" << endl; } while (true) { memset(buf, 0, LEN); int retUsser = recvfrom(client, buf, LEN, 0, (SOCKADDR*)&saServer, &serverSize);     // ....改.... if (retlen == WSAEMSGSIZE) { cout << "over size !" << endl; } else if (retlen == SOCKET_ERROR) { cout << "sendto error !" << endl; } buf[retUsser] = '\0'; cout << buf << endl; gets_s(buf); sendto(client, buf, strlen(buf), 0, (SOCKADDR*)&saServer, sizeof(saServer));     // ....改.... } return 0;}


执行结果:



总结:TCP是面向连接的协议,所以TCP的套接字编程要bind本地套接字地址,进入listen状态,accept得到一个已连接套接字,之后才能接收和发送消息,而UDP是面向无连接的,所以只要bind本地套接字地址之后就可以发送和接收消息了。

服务器要先接收消息再发送信息,而客户端是先发送信息再接收消息。

客服端一般可以省略绑定本地套接字地址,因为系统会自动为客服端分配一个本地IP地址和本地端口。