1.1 创建基本流程
创建一个TCP服务端的程序需要调用的函数流程:
1.2 代码实现
(1) 初始化函数库
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
说明:WSAStartup的参数1->初始化winsock库的版本号;参数2-> 指向WSADATA的指针,而这个结构体用来存储WSAStartup函数调用后返回WindowsSockets数据。在程序的开始处调用该初始化函数,在程序中就可以使用Winsock相关的所有API函数。
(2) 创建套接字
SOCKET sListen = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
说明:socket()函数参数1-> 指定协议族,在windows下使用的有两个,AF_INET,PF_INET,这两个宏在Winsock2.h的定义是相同的。一般在调用socket()函数时应该使用PF_INET,而在设置地址时使用AF_INET。
参数2 -> 指定新套接字描述符的类型,一般使用的有3个,分别是SOCK_STREAM,SOCK_DGRAM和SOCK_RAW,分别是流套接字,数据包套接字和原始协议接口。
参数3 -> 这里用来指定应用程序所使用的通信协议,可以使用的IPPROTO_TCP、IPPROTO_UDP,IPPROTO_ICMP等。此参数是根据参数2值进行选择。这里参数2使用的是SOCK_STREAM,所以参数3使用IPPROTO_TCP。
(3) 绑定套接字与地址信息
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.41.1"); //将点分十进制IP转换为长整数型数
ServerAddr.sin_port = htons(1234); //htons将整形变量从主机字节序转换为网络字节序
bind(sListen,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
说明:socket()函数可以创建新的套接字描述符,但它只是一个描述符,为网络资源做准备。要真正在网络中通信,需要本地的地址与本地的端口号信息。 通过bind函数进行信息的绑定,而bind函数的3个参数当中最重要当属参数2。
它是sockaddr的结构体,该结构体有16个字节,在该结构体之前使用sockaddr_in,为bind函数指定地址和端口时,向sockaddr_in结构体填充相应的内容。
Struct sockaddr_in{
Short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
而这里我们要此结构体的sin_family成员,它的取值有三种:
AF_UNIX(本机通信)
AF_INET(TCP/IP - IPv4)
AF_INET6(TCP/IP – IPv6)
(4) 监听端口
listen(sListen,SOMAXCONN);
说明:参数2 -> 定义了系统中每一个端口最大的监听队列的长度
(5) 获取连接请求
sockaddr_in ClientAddr;
int nSize = sizeof(ClientAddr);
SOCKET sClient = accept(sLisent,(SOCKADDR *)&ClientAddr,&nSize);
说明:accept从请求队列中获取连接信息,创建新的套接字描述符,获取客户端地址。
参数1-> 处于监听端套接字描述符
参数2-> 指向sockaddr结构体的指针,用来返回客户端的地址信息。
参数3-> 客户端sockaddr结构体的大小
(6) 到这里服务端的操作基本完成,只需等待客户端的连接,然后就可以进行数据传输了。
1.3 完整代码
#include
#include
#pragma comment(lib,"ws2_32")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
//创建套接字
SOCKET sListen = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
//对sockaddr_in结构体填充地址,端口等信息
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.41.1"); //将点分十进制IP转换为长整数型数
ServerAddr.sin_port = htons(1234); //htons将整形变量从主机字节序转换为网络字节序
//绑定套接字与地址信息
bind(sListen,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
//端口监听
listen(sListen,SOMAXCONN);
//获取连接请求
sockaddr_in ClientAddr;
int nSize = sizeof(ClientAddr);
SOCKET sClient = accept(sListen,(SOCKADDR *)&ClientAddr,&nSize);
//输出客户端使用的IP地址和端口号
printf("ClientIP=%s:%d\r\n",inet_ntoa(ClientAddr.sin_addr),ntohs(ClientAddr.sin_port));
//ntohs()是一个函数名,作用是将一个16位数由网络字节顺序转换为主机字节顺序
//发送消息
char szMsg[MAXBYTE] = {0};
lstrcpy(szMsg,"Hello Client!\r\n");
send(sClient,szMsg,strlen(szMsg)+sizeof(char),0);
//strlen测长和sizeof测长
//接受消息
recv(sClient,szMsg,MAXBYTE,0);
printf("Client Msg :%s \r\n",szMsg);
WSACleanup();
return 0;
}
2.1 基本流程
客户端和服务端调用的API基本相同:
WSAStartup() -> socket() -> connect() -> send()/recv() -> closesocket() -> WSACleanup()
2.2 代码实现
客户端只需要创建套接字然后填充sockaddr_in结构体的地址和端口等信息即可。
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.41.1");
ServerAddr.sin_port = htons(1234);
此处填充的信息为需要连接的服务端的信息。
2.3 完整代码
#include
#include
#pragma comment (lib,"ws2_32")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
//创建套接字
SOCKET sServer = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
//对sockaddr_in结构体填充地址、端口等信息
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.41.1");
ServerAddr.sin_port = htons(1234);
//连接服务器
connect(sServer,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
char szMsg[MAXBYTE] = {0};
//接收消息
recv(sServer,szMsg,MAXBYTE,0);
printf("Server Msg: %s \r\n",szMsg);
//发送消息
lstrcpy(szMsg,"Hello Server!!!\r\n");
send(sServer,szMsg,sizeof(szMsg)+sizeof(char),0);
WSACleanup();
return 0;
}
基于UDP协议的服务端程序不会去监听端口和等待请求连接,因此UDP协议的服务端程序会较短。
#include
#include
#pragma comment (lib,"ws2_32")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
//创建套接字
SOCKET sServer = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
//对sockaddr_in结构体填充地址、端口等信息
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.41.1");
ServerAddr.sin_port = htons(1245); //htons将整形变量从主机字节序转变为网络字节序
//绑定套接字与地址信息
bind(sServer,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
//接受消息
char szMsg[MAXBYTE] = {0};
struct sockaddr_in ClientAddr;
int nSize = sizeof(ClientAddr);
recvfrom(sServer,szMsg,MAXBYTE,0,(SOCKADDR *)&ClientAddr,&nSize);
printf("Client Msg:%s \r\n",szMsg);
printf("ClientIP = %s:%d\r\n",inet_ntoa(ClientAddr.sin_addr),ntohs(ClientAddr.sin_port));
//发送消息
lstrcpy(szMsg,"Hello Client,this is Server!\r\n");
nSize = sizeof(ClientAddr);
sendto(sServer,szMsg,strlen(szMsg)+sizeof(char),0,(SOCKADDR *)&ClientAddr,nSize);
WSACleanup();
return 0;
}
基于UDP客户端的代码相对于TCP协议的客户端代码来讲,不需要调用connect()函数进行连接,省去了TCP协议的"三次握手"的过程,可以直接发送数据给服务器。
#include
#include
#pragma comment(lib,"ws2_32")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
//创建套接字
SOCKET sClient = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
//对sockaddr_in结构体填充地址、端口等信息
struct sockaddr_in = ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = inet_addr("10.10.30.12");
ServerAddr.sin_port = htons(1234);
//发送消息
char szMsg[MAXBYTE] = {0};
lstrcpy(szMsg,"Hello Server This is Client !");
int nSize = sizeof(ServerAddr);
sendto(sClient,szMsg,strlen(szMsg)+sizeof(char),0,(SOCKADDR *)&ServerAddr,nSize);
//接收消息
nSize = sizeof(ServerAddr);
recvfrom(sClient,szMsg,strlen(szMsg)+sizeof(char),0,(SOCKADDR*)&ServerAddr,nSize);
printf("Server Msg:%s \r\n",szMsg);
WSACleanup();
return 0;
}
学长的博客:https://blog.roachs.cn/