文章目录

  • 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的状态转换:可靠传输,双工操作,面向连接,端到端的传输,可以用于传输大量的数据,确保数据的安全。

服务端步骤

  1. 准备工作,头文件等<winsock2.h> ,#pragma comment(lib,“ws2_32.lib”)
  2. 确定版本信息,确定socket版本,IPV4与IPV6
  3. 创建socket
  4. 初始化协议地址族
  5. 绑定(吧协议地址族与socket绑定在一起)
  6. 监听
  7. 服务端:接受链接
  8. 开始通讯
  9. 关闭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;
}

客户端步骤

  1. 准备工作,头文件等<winsock2.h> ,#pragma comment(lib,“ws2_32.lib”)
  2. 确定版本信息,确定socket版本,IPV4与IPV6
  3. 创建socket
  4. 初始化协议地址族
  5. 链接
  6. 开始通讯(发送send或者接受recv数据)
  7. 关闭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次握手

  1. 客户端向服务端发送信息(报文),SYN,同步标识

  2. 服务端把上步客户端发送的信息SYN发回客户端,再发送一个报文ACK(确认报文)

  3. 客户端回复服务端ACK代表收到

注:服务端是不会主动向客户端发送链接

退出:4次挥手

  1. 客户端发FIN(结束标识)
  2. 服务端收到FIN,发回ACK
  3. 服务端关闭客户端的连接,并再发一个FIN给客户端
  4. 客户端发回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通信

面向非链接,少量的数据传输,速度快。

服务端步骤

  1. 准备工作(头文件和所必须的库等)
  2. 确定版本信息
  3. 创建SOCKET
  4. 初始化协议地址族
  5. 绑定
  6. 通讯
  7. 关闭链接

服务端代码演示如下

//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;
}

客户端步骤

  1. 准备工作(头文件和所必须的库等)
  2. 确定版本信息
  3. 创建SOCKET
  4. 初始化协议地址族
  5. 通讯
  6. 关闭链接

代码演示如下

//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通信相关推荐

  1. win32 socket的一个简单的例子 控制台

     在visual c++里建两个win32控制台工程,分别编译下面的服务端和客户端 服务端: [cpp] view plaincopyprint? #include <windows.h&g ...

  2. 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信

    http://blog.csdn.net/whuancai/article/details/11994341 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通 ...

  3. python 网络编程之Socket通信案例消息发送与接收

    背景 网络编程是python编程中的一项基本技术.本文将实现一个简单的Socket通信案例消息发送与接收 正文 在python中的socket编程的大致流程图如上所示 我们来首先编写客户端的代码: # ...

  4. C# Socket系列三 socket通信的封包和拆包

    通过系列二 我们已经实现了socket的简单通信 接下来我们测试一下,在时间应用的场景下,我们会快速且大量的传输数据的情况! 1 class Program 2 { 3 static void Mai ...

  5. flex java socket通信

    引用:http://developer.51cto.com/art/201003/189791.htm Java socket通信如何进行相关问题的解答呢?还是需要我们不断的学习,在学习的过程中会遇到 ...

  6. Socket通信之操作系统的字节序和位数

    关于Socket通信过程中字节序 在网络编程里,网络字节序是big-endian的,而大部分的PC的系统都是X86处理器系列,X86采用的是little-endian,所以需要将网络数据流转换成本地数 ...

  7. 1.Socket通信

    本博客部分内容参考教程来自C语言中文网. 进入socket通信首先要了解两个概念,第一:服务器端,第二:客户端.(区别是两者的服务对象不同.服务器端是为客户端服务的,客户端就是为真正的"客户 ...

  8. java iso8583 socket 服务_JAVA客户端amp;服务器的socket通信

    JAVA客户端&服务器的socket通信 socket是两台主机之间的一个连接通道,它可以完成七个基本操作: 发送远程机器 发送数据 接收数据 关闭连接 绑定端口 监听入站数据 再绑定端口上接 ...

  9. 手把手教你 Socket 通信(TCP/IP)

    本文将给出完整 Socket 通信代码,在 eclipse 中亲测有效.在Android Studio中用Java开发也是一样的,只是把代码和控件搭配一下,注册监听就好. 网络上的两个程序通过一个双向 ...

最新文章

  1. 「杂谈」同学聚会最悲哀的事情
  2. Xamarin XAML语言教程Xamarin.Forms中改变活动指示器颜色
  3. java项目使用过滤器实例_Java web开发--过滤器篇(详细介绍)
  4. (1)Hadoop 的第一个程序 WordCount 理解
  5. JZOJ 3731. 【NOIP2014模拟7.10】庐州月
  6. jzoj2679-跨时代【背包,dfs,状压】
  7. Nginx平滑升级到最新版本
  8. 生产者消费者代码_生产者消费者模型:Kotlin 多线程读写文件实例
  9. Python交互界面方向键、退格键乱码
  10. android之id统一管理
  11. 当你发现你的Alter报错的时候请看看是不是粗心了
  12. t470键盘拆解_张大妈 ThinkPad T470p 首篇 开箱拆机晒物
  13. Flask渲染Jinja2模板
  14. 高校选课系统的设计与实现
  15. 【iOS安全】iOS应用安全开发总结
  16. 动态3D特效壁纸软件Wallpaper Engine的免费获取及安装问题解决
  17. Bilibili到底有多少御坂妹?(二)
  18. JavaScript canvas
  19. 考核指标在管理中心的重要性
  20. java 修改文件MD5值

热门文章

  1. MFC Windows 程序设计(5)
  2. python搭建个人博客推荐_Python+Django搭建个人博客(4)-Django模板
  3. 《算法导论》算法分析 5种渐近记号 Θ O o Ω ω
  4. 泰国的IPv6功能已从约2%增至30%,部署率位于全球5名
  5. SQL Server中视图,存储过程,注入
  6. 【离散数学】图论-思维导图
  7. 舆情监控数据采集 Scrapy 环境搭建与数据管理方案
  8. YOLOv4: 43mAP/83FPS,虽迟但到,大型调优现场 | 论文速递
  9. stm32常见定时器---TIM定时及PWM输出
  10. 对促销活动效果评估的一些思考