1. 网络通信流程
1.1 流式套接字(TCP)
上图对应代码流程为:
服务端
- 创建套接字(函数:socket)
- 服务器绑定ip和端口号(函数:bind)
- 监听,等待客户端连接(函数:listen)
- 接收客户端连接请求(函数:accept)
- 接收请求数据(函数:recv)
- 发送响应数据(函数:send)
- 关闭套接字(函数:closesocket)
客户端
- 创建套接字(函数:socket)
- 服务器绑定ip和端口号(函数:bind)
- 请求连接(函数:connect)
- 发送响应数据(函数:send)
- 接收请求数据(函数:recv)
- 关闭套接字(函数:closesocket)
示例:
头文件
这里需要特别注意,windows下的头文件如下
//socket头文件
#include "winsock.h"
//socket库的lib
#pragma comment(lib,"ws2_32.lib")
而Linux下头文件则是:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
服务端创建基本流程
//创建套接字,第一个参数表示ip协议族,第二个参数表示数据流方式,第三个参数是TCP协议。
SOCKET socksvr = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == socksvr){
return ;
}
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;//代表Internet协议族
//htons函数是将u_short型变量从主机字节顺序变为TCP/IP网络字节顺序
svraddr.sin_port = htons(5678);
//htonl函数是将u_long型变量从主机字节顺序变为TCP/IP网络字节顺序
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//绑定,将服务器套接字与服务器套接字地址绑定
bind(socksvr,(struct sockaddr *)&svraddr,sizeof(svraddr));//指定名字,类型,长度。绑定套接字
//监听
listen(socksvr,SOMAXCONN);//第一个参数是套接字,第二个参数是等待连接队列的最大长度
//建立客户端套接字地址,主要是为了接受客户端返回参数之用
struct sockaddr_in clientaddr = {0};
int nlen = sizeof(clientaddr);
//以下是建立客户端套接字并建立连接函数,有一个确认过程
SOCKET sockclient = accept(socksvr,(struct sockaddr *)&clientaddr,&nlen);
//先接收后发送,由上面知,数据已在sockclient中,我们只需读此结构便可知晓数据
CHAR szText[100] = {0};
//接收缓冲区数据
recv(sockclient,szText,100,0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。
printf("%s\n",szText);
CHAR szSend[100] = "Hello Client";
send(sockclient,szSend,sizeof(szSend),0);//发送函数。
closesocket(sockclient);
closesocket(socksvr);
recv函数的返回值是接收的数据的字节数。在判断客户端是否已经断开连接的时候,可以根据recv的返回值判断,如果recv的返回值小于等于0,则客户端已经断开了连接。
客户端的流程和服务端基本一样,
//创建socket
SOCKET sockclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == sockclient)
{
return;
}
//连接服务器,建立服务器端套接字地址
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(5678);
//对于inet_addr()函数,它是把“xxx.xxx.xxx.xxx”形式表示的IPV4地址,转换为IN_ADDR结构体能够
//接收的形式(unsigned long型,因为IN_ADDR结构体中的负责接收的S_addr成员变量的类型是unsigned long型)
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本机ip
//向服务器发出连接请求,当然我们也可以通过connet函数的返回值判断到底有无连接成功。
int iRetVal = connect(sockclient,(struct sockaddr*)&addr,sizeof(addr));
//后面发送数据和接收数据和服务端一样,这里省略
1.2 数据报套接字(UDP)
上图对应代码流程为:
服务端
- 创建套接字(函数:socket)
- 服务器绑定ip和端口号(函数:bind)
- 接收请求数据(函数:recvfrom)
- 发送响应数据(函数:sendto)
- 关闭套接字(函数:closesocket)
客户端
- 创建套接字(函数:socket)
- 服务器绑定ip和端口号(函数:bind)
- 发送响应数据(函数:sendto)
- 接收请求数据(函数:recvfrom)
- 关闭套接字(函数:closesocket)
示例:
头文件
//socket头文件
#include "winsock.h"
//socket库的lib
#pragma comment(lib,"ws2_32.lib")
服务端创建基本流程
//创建套接字,第一个参数表示ip协议族,第二个参数表示数据流方式,第三个参数是TCP协议。
SOCKET socksvr = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == socksvr){
return ;
}
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;//代表Internet协议族
//htons函数是将u_short型变量从主机字节顺序变为TCP/IP网络字节顺序
svraddr.sin_port = htons(5678);
//htonl函数是将u_long型变量从主机字节顺序变为TCP/IP网络字节顺序
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//绑定,将服务器套接字与服务器套接字地址绑定
bind(socksvr,(struct sockaddr *)&svraddr,sizeof(svraddr));//指定名字,类型,长度。绑定套接字
CHAR szRecv[100] = {0};
struct sockaddr_in clientaddr = {0};
int nLen = sizeof(clientaddr);
/*下面函数前四个参数同TCP接收数据函数recv()一样,后两个中,一个是返回发送*******/
/*数据地址的主机的地址,包括IP地址以及端口号,最后一个为地址长度的地址。*******/
/*此函数中,先是服务器端的套接字,后是客户端的地址*/
//从后往前读此函数
recvfrom(socksvr,szRecv,100,0,(struct sockaddr*)&clientaddr,&nLen);//构造ip地址
printf("%s\n",szRecv);
//注1:该程序也可以向客户端发送数据。
//注2:服务器端中,必须也是先接收后发送,不然,我们无法知道客户端的地址。下面函数中clientaddr已知晓
CHAR szSend[100] = "hello udp client";
//从前往后读此函数
sendto(socksvr,szSend,100,0,(struct sockaddr*)&clientaddr,nLen);//发送时构造ip地址和端口。
//关闭socket
closesocket(socksvr);
客户端基本一致,只是bind可以省略
//创建SOCKET ,ip协议族,数据报方式,udp协议。
SOCKET sockclient = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(INVALID_SOCKET == sockclient)
{
return ;
}
//数据收发,服务器端套接字地址
struct sockaddr_in svraddr = {0};
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(5780);
svraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定服务器端的IP与端口。
CHAR szSend[100] = "hello udp server";
/*此函数先是客户端的套接字,然后是服务器端地址*/
//简单理解为:从函数前面的客户端套接字的发送数据缓存区中将数发送给服务器端地址
sendto(sockclient,szSend,100,0,(struct sockaddr*)&svraddr,sizeof(svraddr));//发送时构造ip地址和端口。
//注:该程序也可以接收服务器端回传的数据。
CHAR szRecv[100];
//简单理解为:从函数后面的服务器端地址中取数到客户端套接字的接收缓冲区szRecv中
int len = sizeof(svraddr);
recvfrom(sockclient,szRecv,100,0,(struct sockaddr*)&svraddr,&len);
printf("%s \n",szRecv);
//关闭socket
closesocket(sockclient);
参考博客
C++ TCP/IP通信