win32 socket通信
文章目录
- SOCKET套接字
- socket函数
- SOCKADDR_IN结构体
- bind()函数
- connect()函数
- listen()函数
- accept()函数
- send()/recv()和sendto()/recvfrom()函数
- TCP通信
- 服务端步骤
- 客户端步骤
- TCP的11种状态
- UDP通信
- 服务端步骤
- 客户端步骤
一般分为Server端(服务端)和Client端(客户端)
基本架构
按协议来划分分为七层
- 应用层:各种应用,如VirtualBox,QQ等各种应用
- 表示层:代码转换,数据的加密等
- 会话层:用户与服务端,用户与用户之间的链接等
- 传输层:数据的传输(TCP,UDP等协议)
- 网络层:数据选择端口,IP地址等
- 数据链路层
- 物理层
按架构来划分为五层
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
SOCKET套接字
源IP与目标IP,源端口与目标端口组合为套接字
socket():创建套接字。
bind():指定本地地址。一个套接字用socket()创建后,它其实还没有与任何特定的本地或目的地址相关联。在很多情况下,应用程序并不关心它们使用的本地地址,这时就可以不用调用bind指定本地的地址,而由协议软件为它们选择一个。但是,在某个知名端口(Well-known Port)上操作的服务器进程必须要对系统指定本地端口。所以一旦创建了一个套接字,服务器就必须使用bind()系统调用为套接字建立一个本地地址。
connect():将套接字连接到目的地址。初始创建的套接字并未与任何外地目的地址关联。客户机可以调用connect()为套接字绑定一个永久的目的地址,将它置于已连接状态。对数据流方式的套接字,必须在传输数据前,调用connect()构造一个与目的地的TCP连接,并在不能构造连接时返回一个差错代码。如果是数据报方式,则不是必须在传输数据前调用connect。如果调用了connect(),也并不像数据流方式那样发送请求建连的报文,而是只在本地存储目的地址,以后该socket上发送的所有数据都送往这个地址,程序员就可以免去为每一次发送数据都指定目的地址的麻烦。
listen():设置等待连接状态。对于一个服务器的程序,当申请到套接字,并调用bind()与本地地址绑定后,就应该等待某个客户机的程序来要求连接。listen()就是把一个套接字设置为这种状态的函数。
accept():接受连接请求。服务器进程使用系统调用socket,bind和listen创建一个套接字,将它绑定到知名的端口,并指定连接请求的队列长度。然后,服务器调用accept进入等待状态,直到到达一个连接请求。
send()/recv()和sendto()/recvfrom():发送和接收数据 。在数据流方式中,一个连接建立以后,或者在数据报方式下,调用了connect()进行了套接字与目的地址的绑定后,就可以调用send()和reev()函数进行数据传输。
closesocket():关闭套接字。
socket函数
//创建套接字 socket()任何用户要进行通信都必须创建套接字,创建套接字是通过系统调用socket()函数实现的。
SOCKET PASCAL FAR socket (_In_ int af, //协议族_In_ int type, //表示类型_In_ int protocol); //指定协议
af指定套接字使用的协议族。也就是说,利用它来分辨地址的类型。
UNIX支持的协议族有:UNIXDomain(AF_UNIX)、In-temet(AF_INET)、XeroxNS(AF_NS)等。
而Dos和Windows仅支持AF_INET。
type参数指定所需的通信类型。包括数据流(SOCK_STREAM)、数据报(SOCK-DGRAM)和原始类型(S0CK_RAW)。
protocol说明该套接字使用的协议族中的特定协议。如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。
值 | 描述 |
---|---|
IPPROTO_IP | 0 虚拟IP |
IPPROTO_ICMP | 1 控制消息协议 |
IPPROTO_IGMP | 2 组管理协议 |
IPPROTO_GGP | 3 网关(以弃用) |
IPPROTO_TCP | 6 TCP传输协议 |
IPPROTO_PUP | 12 PUP传输协议 |
IPPROTO_UDP | 17 UDP用户数据报协议 |
IPPROTO_IDP | 22 |
IPPROTO_ND | 77 |
该函数调用成功,返回一个套接字描述符SOCKET,该函数调用失败,返回值为INVALID_SOCKET。
SOCKADDR_IN结构体
typedef struct sockaddr_in {
#if(_WIN32_WINNT < 0x0600)short sin_family;
#else //(_WIN32_WINNT < 0x0600)ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)USHORT sin_port;IN_ADDR sin_addr;CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
bind()函数
int PASCAL FAR bind( SOCKET sockaddr, //表示已经建立的socket编号const struct sockaddr FAR* my_addr,//指向sockaddr结构体类型的指针int addrlen);//表示my_addr结构的长度,可以用sizeof操作符获得
如无错误发生,则bind()返回0。
否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。
值 | 描述 |
---|---|
WSANOTINITIALISED | 在使用此API之前应首先成功地调用WSAStartup()。 |
WSAENETDOWN | 套接口实现检测到网络子系统失效。 |
WSAEADDRINUSE | 所定端口已在使用中(参见setoption()中的SO_REUSEADDR选项)。 |
WSAEFAULT | namelen参数太小(小于sockaddr结构的大小)。 |
WSAEINPROGRESS | 一个阻塞的套接口调用正在运行中。 |
WSAEAFNOSUPPORT | 本协议不支持所指定的地址族。 |
WSAEINVAL | 该套接口已与一个地址捆绑。 |
WSAENOBUFS | 无足够可用缓冲区,连接过多。 |
WSAENOTSOCK | 描述字不是一个套接口。 |
connect()函数
int connect(SOCKET s, //标识一个未连接socketconst struct sockaddr * name,//指向要连接套接字的sockaddr结构体的指针int namelen); //sockaddr结构体的字节长度
返回的错误码解释
值 | 描述 |
---|---|
EBADF | 参数sockfd 非合法socket处理代码 |
EFAULT | 参数serv_addr指针指向无法存取的内存空间 |
ENOTSOCK | 参数sockfd为一文件描述词,非socket。 |
EISCONN | 参数sockfd的socket已是连线状态 |
ECONNREFUSED | 连线要求被server端拒绝。 |
ETIMEDOUT | 企图连线的操作超过限定时间仍未有响应。 |
ENETUNREACH | 无法传送数据包至指定的主机。 |
EAFNOSUPPORT | sockaddr结构的sa_family不正确。 |
EALREADY | socket为不可阻塞且先前的连线操作还未完成。 |
listen()函数
int listen(SOCKET sockfd, //一个已绑定未被连接的套接字描述符int backlog); //连接请求队列的最大长度一般2到4,用SOMAXCONN则由系统确定。
无错误,返回0,
否则,返回SOCKET ERROR,windows上可以调用函数WSAGetLastError取得错误代码,在Linux可使用errno。
accept()函数
SOCKET accept(int sockfd, //套接字描述符,该套接口在listen()后监听连接。struct sockaddr *addr, //指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址,Addr参数的实际格式由套接口创建时所产生的地址族确定。socklen_t *addrlen); //(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数
如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。
addrlen所指的整形数初始时包含addr所指地址空间的大小,在返回时它包含实际返回地址的字节长度。
send()/recv()和sendto()/recvfrom()函数
send()函数
向一个已连接的套接口发送数据。
适用于已连接的数据包或流式套接口发送数据。对于数据报类套接口,必需注意发送数据长度不应超过通讯子网的IP包最大长度。IP包最大长度在WSAStartup()调用返回的WSAData的iMaxUdpDg元素中。如果数据太长无法自动通过下层协议,则返回WSAEMSGSIZE错误,数据不会被发送。
//send()函数
int PASCAL FAR send(SOCKET s, //一个用于标识已连接套接口的描述字const char FAR* buf, //包含待发送数据的缓冲区int len, //缓冲区中数据的长度int flags); //调用执行方式
若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
flags 参数有如下的选择:
值 | 描述 |
---|---|
MSG_DONTROUTE | 勿将数据路由出本地网络 |
MSG_DONTWAIT | 允许非阻塞操作(等价于使用O_NONBLOCK) |
MSG_EOR | 如果协议支持,此为记录结束 |
MSG_OOB | 如果协议支持,发送带外数据 |
MSG_NOSIGNAL | 禁止向系统发送异常信息 |
recv()函数
本函数用于已连接的数据报或流式套接口进行数据的接收。
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;
int recv(_In_ SOCKET s, //指定接收端套接字描述符_Out_ char *buf, //指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据_In_ int len, //指明buf的长度_In_ int flags); //这个参数一般置0。
/*
第四个参数:
MSG_PEEK 查看当前数据。数据将被复制到缓冲区中,但并不从输入队列中删除。
MSG_OOB 处理[带外数据](https://baike.baidu.com/item/带外数据)*/
若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
sendto()函数
指向一指定目的地发送数据,sendto()适用于发送未建立连接的UDP数据包 (参数为SOCK_DGRAM)。
int PASCAL FAR sendto (_In_ SOCKET s,//套接字_In_reads_bytes_(len) const char FAR * buf,//待发送数据的缓冲区_In_ int len,//缓冲区长度_In_ int flags,//调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式_In_reads_bytes_opt_(tolen) const struct sockaddr FAR *to,//(可选)指针,指向目的套接字的地址_In_ int tolen);//所指地址的长度
返回为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
recvfrom()函数
本函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。
int PASCAL FAR recvfrom (_In_ SOCKET s, //标识一个已连接套接口的描述字。char FAR * buf, //接收数据缓冲区。_In_ int len, //缓冲区长度。_In_ int flags, //调用操作方式。0表示一次收发struct sockaddr FAR * from,//(可选)指针,指向保存来源地址的缓冲区。int FAR * fromlen); //(可选)指针,指向from缓冲区长度值。
若无错误发生,recvfrom()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
与recv()函数的比较:UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明目的地址。从套接字上接收一个消息。对于recvfrom ,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。不管是recv还是recvfrom,都有两种模式,阻塞和非阻塞,可以通过ioctl函数来设置。阻塞模式是一直等待直到有数据到达,非阻塞模式是立即返回,需要通过消息,异步事件等来查询完成状态。
TCP通信
建立稳定链接(c/s)模型,一对一
TCP的状态转换:可靠传输,双工操作,面向连接,端到端的传输,可以用于传输大量的数据,确保数据的安全。
服务端步骤
- 准备工作,头文件等<winsock2.h> ,#pragma comment(lib,“ws2_32.lib”)
- 确定版本信息,确定socket版本,IPV4与IPV6
- 创建socket
- 初始化协议地址族
- 绑定(吧协议地址族与socket绑定在一起)
- 监听
- 服务端:接受链接
- 开始通讯
- 关闭socket
服务端代码演示如下
//1.头文件
#include <stdio.h>
#include <Winsock2.h>
#pragma comment (lib,"ws2_32.lib")int main()
{//2.确定版本信息WSADATA wsaData;//异步套接字的启动命令//参数一:请求哪个版本,高阶字段指的修订版本,低阶字段指的是主版本号//参数二:是一个结构体,用来接受socket实现的细节WSAStartup(MAKEWORD(2, 2), //版本请求,2.2&wsaData); //wsaData保存版本信息的结构体if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("请求版本失败!\n");return -1;}printf("请求版本成功!\n");//3.创建socket//参数一:协议族,表示当前socket要用什么类型的地址和端口,AF_INET表示用IPV4地址(32位),端口号16位//参数二:表示类型,需要指定为SOCK_STREAM,流式SOCKET,面向连接//参数三:制定协议,IPPROTO_TCP,表示TCP协议SOCKET serverScoket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if (INVALID_SOCKET == serverScoket){printf("创建套接字失败!\n");WSACleanup(); //关闭套接字请求return -1;}printf("创建套接字成功!\n");//4.初始化协议地址族SOCKADDR_IN serverAddr = { 0 }; //初始化协议地址serverAddr.sin_family = AF_INET; //此参数必须和上步骤中创建socket一致//端口的形式网络传输中和PC的端口存储方式不一致//小端:先存低位再存高位 PC中存储是如此//大端:先存高位再存低位 网络传输如此//所以需要通过函数htons把PC端口的数值转换为网络端口的数值serverAddr.sin_port = htons(8888);//指定端口号//因为IP是点分格式的字符串,所以需要用inet_addr来转换为整数serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.103"); //指定服务端IP//5.绑定//参数一:socket的名字//参数二:协议地址族的首地址//参数三:协议地址族的长度//返回值:返回绑定状态if (SOCKET_ERROR == bind(serverScoket, (SOCKADDR *)&serverAddr, sizeof(serverAddr))){printf("绑定失败!\n");closesocket(serverScoket);//关闭套接字socketWSACleanup(); //关闭套接字请求return -1;}printf("绑定成功!\n");//6.监听//参数一:监听的socket//参数二:监听的个数,是等待连接的队列最大长度if (SOCKET_ERROR == listen(serverScoket, 10)){printf("监听失败!\n");closesocket(serverScoket);//关闭套接字socketWSACleanup(); //关闭套接字请求return -1;}printf("监听成功!\n");//7.服务端接受链接//参数一:连接的socket//参数二:可以给nullptr,表示不保存连接进来的客户端的ip地址等信息//参数三:可以给nullptr//如果2,3 不给nullptr,表示保存连接进来的客户端ip地址信息SOCKADDR_IN clientAddr = { 0 }; //用来保存客户端的信息int len = sizeof(clientAddr);SOCKET clientSocket = accept(serverScoket, (sockaddr*)&clientAddr, &len);if (INVALID_SOCKET == clientSocket){printf("接受链接失败!\n");closesocket(serverScoket);//关闭套接字socketWSACleanup(); //关闭套接字请求return -1;}printf("接受客户链接成功!\n");printf("客户ip为:%s", inet_ntoa(clientAddr.sin_addr));//8.开始通讯char recvbuff[1024] = {}; //接受数据char sendbuff[1024] = {}; //发送数据//参数一:代表客户端的socket,表示从客户端进行收取数据//参数二:接受的数据存放地址//参数三:接受数据的长度//参数四:表示收发方式,0表示默认,一次收完while (true){//保存数据清空memset(recvbuff, 0, sizeof(recvbuff));//从客户端接受数据if (recv(clientSocket, recvbuff, sizeof(recvbuff) - 1, 0) > 0){printf("客户说:%s\n", recvbuff);}else{break;}memset(sendbuff, 0, sizeof(sendbuff));printf("我说:");scanf_s("%s", sendbuff,sizeof(sendbuff)-1);//发送数据给客户端send(clientSocket, sendbuff, strlen(sendbuff), 0);}//9.关闭链接closesocket(clientSocket);//关闭客户端socketclosesocket(serverScoket);//关闭服务端socketWSACleanup(); //关闭套接字请求return 0;
}
客户端步骤
- 准备工作,头文件等<winsock2.h> ,#pragma comment(lib,“ws2_32.lib”)
- 确定版本信息,确定socket版本,IPV4与IPV6
- 创建socket
- 初始化协议地址族
- 链接
- 开始通讯(发送send或者接受recv数据)
- 关闭socket
代码演示如下
//1.头文件
#include <stdio.h>
#include <Winsock2.h>
#pragma comment (lib,"ws2_32.lib")int main()
{//2.确定版本信息WSADATA wsaData;//异步套接字的启动命令//参数一:请求哪个版本,高阶字段指的修订版本,低阶字段指的是主版本号//参数二:是一个结构体,用来接受socket实现的细节WSAStartup(MAKEWORD(2, 2), //版本请求,2.2&wsaData); //wsaData保存版本信息的结构体if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("请求版本失败!");return -1;}printf("请求版本成功!");//3.创建socket//参数一:协议族,表示当前socket要用什么类型的地址和端口,AF_INET表示用IPV4地址(32位),端口号16位//参数二:表示类型,需要指定为SOCK_STREAM,流式SOCKET,面向连接//参数三:制定协议,IPPROTO_TCP,表示TCP协议SOCKET clientScoket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == clientScoket){printf("创建套接字失败!\n");WSACleanup(); //关闭套接字请求return -1;}printf("创建套接字成功!\n");//4.初始化协议地址族SOCKADDR_IN clientrAddr = { 0 }; //初始化协议地址clientrAddr.sin_family = AF_INET; //此参数必须和上步骤中创建socket一致//端口的形式网络传输中和PC的端口存储方式不一致//小端:先存低位再存高位 PC中存储是如此//大端:先存高位再存低位 网络传输如此//所以需要通过函数htons把PC端口的数值转换为网络端口的数值clientrAddr.sin_port = htons(8888);//指定端口号//因为IP是点分格式的字符串,所以需要用inet_addr来转换为整数clientrAddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.103"); //指定服务端IP 客户端链接的也是服务端的IP//5.链接if (SOCKET_ERROR == connect(clientScoket, (SOCKADDR *)&clientrAddr, sizeof(clientrAddr))){printf("链接失败!\n");closesocket(clientScoket);//关闭客户端socketWSACleanup(); //关闭套接字请求return -1;}printf("链接成功!\n");//接受服务器发送的数据/*int len = sizeof(clientrAddr);SOCKET servertSocket = accept(clietnScoket, (sockaddr*)&clientrAddr, &len);*///6.发送char sendBuff[1024] = { 0 };char recvBuff[1024] = { 0 };while (true){memset(sendBuff, 0, sizeof(sendBuff));scanf_s("%s", sendBuff, sizeof(sendBuff) - 1);//发送数据到服务器if (send(clientScoket, sendBuff, strlen(sendBuff), 0) > 0){}else{break;}memset(recvBuff, 0, sizeof(recvBuff));//从服务器接受数据if (recv(clientScoket, recvBuff, sizeof(recvBuff) - 1, 0) > 0){printf("服务器说:%s\n", recvBuff);}}//7.关闭链接closesocket(clientScoket);//关闭客户端socketWSACleanup(); //关闭套接字请求return 0;
}
TCP的11种状态
单工:A可以发B,B不能发给A,类似收音机
半双工:A可以发B,B可以发A,但不能同时,同时会冲突,类似对讲机
双工:A可以发B,B可以发A,且可以同时互相发送,类似电话
链接:3次握手
客户端向服务端发送信息(报文),SYN,同步标识
服务端把上步客户端发送的信息SYN发回客户端,再发送一个报文ACK(确认报文)
客户端回复服务端ACK代表收到
注:服务端是不会主动向客户端发送链接
退出:4次挥手
- 客户端发FIN(结束标识)
- 服务端收到FIN,发回ACK
- 服务端关闭客户端的连接,并再发一个FIN给客户端
- 客户端发回ACK确认断开链接
11种状态如下
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqZgffA3-1588004270823)(L:\C++\TCP.png)]
简单解释:
l CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。
l LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。
l SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。
l SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。
l ESTABLISHED :表示TCP连接已经成功建立。
l FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。
l FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。
l TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)
l CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。
l CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
l LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。
UDP通信
面向非链接,少量的数据传输,速度快。
服务端步骤
- 准备工作(头文件和所必须的库等)
- 确定版本信息
- 创建SOCKET
- 初始化协议地址族
- 绑定
- 通讯
- 关闭链接
服务端代码演示如下
//1.头文件
#include <stdio.h>
#include <Winsock2.h>
#pragma comment (lib,"ws2_32.lib")int main()
{//2.确定版本信息WSADATA wsaData;//异步套接字的启动命令//参数一:请求哪个版本,高阶字段指的修订版本,低阶字段指的是主版本号//参数二:是一个结构体,用来接受socket实现的细节WSAStartup(MAKEWORD(2, 2), //版本请求,2.2&wsaData); //wsaData保存版本信息的结构体if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("请求版本失败!\n");return -1;}printf("请求版本成功!\n");//3.创建socket//参数一:协议族,表示当前socket要用什么类型的地址和端口,AF_INET表示用IPV4地址(32位),端口号16位//参数二:表示类型,需要指定为SOCK_DGRAM,数据报文式socket,帧传输//参数三:制定协议,需要指定为IPPROTO_UDP,表示UDP协议SOCKET serverScoket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (INVALID_SOCKET == serverScoket){printf("创建套接字失败!\n");WSACleanup(); //关闭套接字请求return -1;}printf("创建套接字成功!\n");//4.初始化协议地址族SOCKADDR_IN serverAddr = { 0 }; //初始化协议地址serverAddr.sin_family = AF_INET; //此参数必须和上步骤中创建socket一致//端口的形式网络传输中和PC的端口存储方式不一致//小端:先存低位再存高位 PC中存储是如此//大端:先存高位再存低位 网络传输如此//所以需要通过函数htons把PC端口的数值转换为网络端口的数值serverAddr.sin_port = htons(8898);//指定端口号//因为IP是点分格式的字符串,所以需要用inet_addr来转换为整数serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.103"); //指定服务端IP//5.绑定//参数一:socket的名字//参数二:协议地址族的首地址//参数三:协议地址族的长度//返回值:返回绑定状态if (SOCKET_ERROR == bind(serverScoket, (SOCKADDR *)&serverAddr, sizeof(serverAddr))){printf("绑定失败!\n");closesocket(serverScoket);//关闭套接字socketWSACleanup(); //关闭套接字请求return -1;}printf("绑定成功!\n");//6.通讯char recvBuff[1024] = {}; //接受数据char sendBuff[1024] = {}; //发送数据SOCKADDR_IN clientAddr = { 0 }; //用来保存客户端的信息int len = sizeof(clientAddr);while (true){//接受信息if (recvfrom(serverScoket, recvBuff, sizeof(recvBuff) - 1, 0, (sockaddr*)&clientAddr, &len)>0){printf("发送信息的ip为:%s\n", inet_ntoa(clientAddr.sin_addr));printf("发送的数据:%s\n", recvBuff);memset(recvBuff, 0, sizeof(recvBuff));}//发送数据给客户端memset(sendBuff, 0, sizeof(sendBuff));scanf_s("%s", sendBuff, sizeof(sendBuff) - 1);//发送数据到服务器//sendto的第5,6个参数必须写,因为没有链接if (sendto(serverScoket, sendBuff, strlen(sendBuff), 0, (sockaddr*)&clientAddr, sizeof(clientAddr)) > 0){printf("数据发送成功!\n");}else{break;}}//7.关闭链接closesocket(serverScoket);//关闭服务端socketWSACleanup(); //关闭套接字请求return 0;
}
客户端步骤
- 准备工作(头文件和所必须的库等)
- 确定版本信息
- 创建SOCKET
- 初始化协议地址族
- 通讯
- 关闭链接
代码演示如下
//1.头文件
#include <stdio.h>
#include <Winsock2.h>
#pragma comment (lib,"ws2_32.lib")int main()
{//2.确定版本信息WSADATA wsaData;//异步套接字的启动命令//参数一:请求哪个版本,高阶字段指的修订版本,低阶字段指的是主版本号//参数二:是一个结构体,用来接受socket实现的细节WSAStartup(MAKEWORD(2, 2), //版本请求,2.2&wsaData); //wsaData保存版本信息的结构体if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("请求版本失败!\n");return -1;}printf("请求版本成功!\n");//3.创建socket//参数一:协议族,表示当前socket要用什么类型的地址和端口,AF_INET表示用IPV4地址(32位),端口号16位//参数二:表示类型,需要指定为SOCK_DGRAM,数据报文式socket,帧传输//参数三:制定协议,需要指定为IPPROTO_UDP,表示UDP协议SOCKET clientScoket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (INVALID_SOCKET == clientScoket){printf("创建套接字失败!\n");WSACleanup(); //关闭套接字请求return -1;}printf("创建套接字成功!\n");//4.初始化协议地址族SOCKADDR_IN serverAddr = { 0 }; //初始化协议地址serverAddr.sin_family = AF_INET; //此参数必须和上步骤中创建socket一致//端口的形式网络传输中和PC的端口存储方式不一致//小端:先存低位再存高位 PC中存储是如此//大端:先存高位再存低位 网络传输如此//所以需要通过函数htons把PC端口的数值转换为网络端口的数值serverAddr.sin_port = htons(8898);//指定端口号//因为IP是点分格式的字符串,所以需要用inet_addr来转换为整数serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.103"); //指定服务端IP//5.通讯char recvBuff[1024] = {}; //接受数据char sendBuff[1024] = {}; //发送数据//发送信息while (true){memset(recvBuff, 0, sizeof(recvBuff));scanf_s("%s", sendBuff, sizeof(sendBuff) - 1);//发送数据到服务器//sendto的第5,6个参数必须写,因为没有链接if (sendto(clientScoket, sendBuff, strlen(sendBuff), 0, (sockaddr*)&serverAddr, sizeof(serverAddr)) > 0){printf("数据发送成功!\n");}else{break;}//接受信息if (recvfrom(clientScoket, recvBuff, sizeof(recvBuff) - 1, 0, 0, 0)>0){printf("发送信息的ip为:%s\n", inet_ntoa(serverAddr.sin_addr));printf("收到的数据:%s\n", recvBuff);memset(recvBuff, 0, sizeof(recvBuff));}}//7.关闭链接closesocket(clientScoket);//关闭客户端socketWSACleanup(); //关闭套接字请求return 0;
}
win32 socket通信相关推荐
- win32 socket的一个简单的例子 控制台
在visual c++里建两个win32控制台工程,分别编译下面的服务端和客户端 服务端: [cpp] view plaincopyprint? #include <windows.h&g ...
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
http://blog.csdn.net/whuancai/article/details/11994341 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通 ...
- python 网络编程之Socket通信案例消息发送与接收
背景 网络编程是python编程中的一项基本技术.本文将实现一个简单的Socket通信案例消息发送与接收 正文 在python中的socket编程的大致流程图如上所示 我们来首先编写客户端的代码: # ...
- C# Socket系列三 socket通信的封包和拆包
通过系列二 我们已经实现了socket的简单通信 接下来我们测试一下,在时间应用的场景下,我们会快速且大量的传输数据的情况! 1 class Program 2 { 3 static void Mai ...
- flex java socket通信
引用:http://developer.51cto.com/art/201003/189791.htm Java socket通信如何进行相关问题的解答呢?还是需要我们不断的学习,在学习的过程中会遇到 ...
- Socket通信之操作系统的字节序和位数
关于Socket通信过程中字节序 在网络编程里,网络字节序是big-endian的,而大部分的PC的系统都是X86处理器系列,X86采用的是little-endian,所以需要将网络数据流转换成本地数 ...
- 1.Socket通信
本博客部分内容参考教程来自C语言中文网. 进入socket通信首先要了解两个概念,第一:服务器端,第二:客户端.(区别是两者的服务对象不同.服务器端是为客户端服务的,客户端就是为真正的"客户 ...
- java iso8583 socket 服务_JAVA客户端amp;服务器的socket通信
JAVA客户端&服务器的socket通信 socket是两台主机之间的一个连接通道,它可以完成七个基本操作: 发送远程机器 发送数据 接收数据 关闭连接 绑定端口 监听入站数据 再绑定端口上接 ...
- 手把手教你 Socket 通信(TCP/IP)
本文将给出完整 Socket 通信代码,在 eclipse 中亲测有效.在Android Studio中用Java开发也是一样的,只是把代码和控件搭配一下,注册监听就好. 网络上的两个程序通过一个双向 ...
最新文章
- 「杂谈」同学聚会最悲哀的事情
- Xamarin XAML语言教程Xamarin.Forms中改变活动指示器颜色
- java项目使用过滤器实例_Java web开发--过滤器篇(详细介绍)
- (1)Hadoop 的第一个程序 WordCount 理解
- JZOJ 3731. 【NOIP2014模拟7.10】庐州月
- jzoj2679-跨时代【背包,dfs,状压】
- Nginx平滑升级到最新版本
- 生产者消费者代码_生产者消费者模型:Kotlin 多线程读写文件实例
- Python交互界面方向键、退格键乱码
- android之id统一管理
- 当你发现你的Alter报错的时候请看看是不是粗心了
- t470键盘拆解_张大妈 ThinkPad T470p 首篇 开箱拆机晒物
- Flask渲染Jinja2模板
- 高校选课系统的设计与实现
- 【iOS安全】iOS应用安全开发总结
- 动态3D特效壁纸软件Wallpaper Engine的免费获取及安装问题解决
- Bilibili到底有多少御坂妹?(二)
- JavaScript canvas
- 考核指标在管理中心的重要性
- java 修改文件MD5值
热门文章
- MFC Windows 程序设计(5)
- python搭建个人博客推荐_Python+Django搭建个人博客(4)-Django模板
- 《算法导论》算法分析 5种渐近记号 Θ O o Ω ω
- 泰国的IPv6功能已从约2%增至30%,部署率位于全球5名
- SQL Server中视图,存储过程,注入
- 【离散数学】图论-思维导图
- 舆情监控数据采集 Scrapy 环境搭建与数据管理方案
- YOLOv4: 43mAP/83FPS,虽迟但到,大型调优现场 | 论文速递
- stm32常见定时器---TIM定时及PWM输出
- 对促销活动效果评估的一些思考