在windows平台下进行socket编程,其实有一些步骤是固定不变的,只要遵循这些流程就不会被那些繁琐的网络API、数据结构弄晕。
完整的服务器端和客户端源代码,可以去这里下载(点击打开链接)。
这里就以WinSocket中的TCP通信为例,编写一个客户端,向服务器发送一个消息,编写一个服务器端,向客户端反射一个消息,以此对详细的编程步骤进行说明。
一、服务器端
1、首先配置好WinSocket编程环境,如引用头文件、链接静态库等
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")
2、初始化WinSocket库
// 协商套接字版本信息,初始化套接字库
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d", iResult);
return 1;
}
3、创建用于监听客户端连接请求的socket,注意此socket并不用于进行数据传输
// 创建用于监听的套接字
SOCKET ServSocket = INVALID_SOCKET;
ServSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (ServSocket == INVALID_SOCKET)
{
printf("socket failed with error: %d", WSAGetLastError());
WSACleanup();
return 1;
}
4、指定监听socket的地址和端口号,客户端便可以连接到该socket
// 为监听套接字绑定地址和端口号
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_addr.S_un.S_addr = INADDR_ANY;
addrServ.sin_port = htons(DEFAULT_PORT);
iResult = bind(ServSocket, (SOCKADDR*)&addrServ, sizeof(SOCKADDR_IN));
if (iResult == SOCKET_ERROR)
{
printf("bind faield with error: %d\n", WSAGetLastError());
closesocket(ServSocket);
WSACleanup();
return 1;
}
5、将监听socket设置为监听状态
// 开启监听状态
iResult = listen(ServSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
{
printf("listen faield with error: %d\n", WSAGetLastError());
closesocket(ServSocket);
WSACleanup();
return 1;
}
printf("server is listening ......\n");
6、接收客户端发来的连接请求,并保存用于标识客户端的已连接socket,以便后面能利用该socket与该客户端进行通信,这一步默认情况下会阻塞主线程
// 接收客户端的链接请求,并且返回已连接套接字,负责与本次连接的客户端通信
SOCKET AcceSocket = INVALID_SOCKET;
SOCKADDR_IN addrClient;
int addrClienLen = sizeof(SOCKADDR_IN);
AcceSocket = accept(ServSocket, (SOCKADDR*)&addrClient, &addrClienLen);
if (AcceSocket == INVALID_SOCKET)
{
printf("accept failed with error: %d\n", WSAGetLastError());
break;
}
printf("a cilent has connect successful, its ip: %s\n", inet_ntoa(addrClient.sin_addr));
7、利用已连接socket接收客户端发送的消息,这一步默认情况下也会阻塞主线程
// 接收客户端发送的数据
char recvBuf[DEFAULT_BUF_LEN];
memset(recvBuf, 0, sizeof(recvBuf));
iResult = recv(AcceSocket, recvBuf, DEFAULT_BUF_LEN, 0);
// 成功接收到数据
if (iResult > 0)
{
printf("%d bytes data received: ", iResult);
printf("%s\n", recvBuf);
closesocket(AcceSocket);
}
// 连接关闭
else if (iResult == 0)
{
printf("current connection closing\n");
closesocket(AcceSocket);
}
// 接收发生错误
else
{
printf("recv failed with error: %d\n", WSAGetLastError());
closesocket(AcceSocket);
break;
}
8、利用已连接socket向客户端回射一个消息,发送操作默认也会阻塞
// 回射一个消息
char sendBuf[DEFAULT_BUF_LEN] = "this is server";
iResult = send(AcceSocket, sendBuf, strlen(sendBuf), 0);
if (iResult == SOCKET_ERROR)
{
printf("send faield with error: %d\n", WSAGetLastError());
closesocket(AcceSocket);
}
二、客户端
相比服务器端的编程步骤,客户端的创建少了对监听socket的操作,多了对服务器端地址的解析,以及发起连接请求的操作。
第1、2步的引入环境、初始化库同服务器端
3、根据输入的服务器端地址,解析出其ip地址,用于进行正常通信
// 解析服务器地址信息
char pstrHost[512];
printf("please input a host: ");
scanf("%s", pstrHost);
LPHOSTENT lpHost = gethostbyname(pstrHost);
if (lpHost == NULL)
{
printf("host not found\n");
WSACleanup();
return 1;
}
// 获得目的ip地址
SOCKADDR_IN addrDest;
addrDest.sin_family = AF_INET;
addrDest.sin_addr.S_un.S_addr = *((ULONG*)(lpHost->h_addr_list[0]));
addrDest.sin_port = htons(DEFAULT_PORT);
4、创建客户端socket,用于与服务器端进行通信
// 创建客户端套接字,用于连接和通信
SOCKET ConnecSocket = INVALID_SOCKET;
ConnecSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (ConnecSocket == INVALID_SOCKET)
{
printf("socket failed with error: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
5、向服务器端发起连接请求,建立通信连接
// 向服务器发起连接请求
iResult = connect(ConnecSocket, (SOCKADDR*)&addrDest, sizeof(SOCKADDR_IN));
if (iResult == SOCKET_ERROR)
{
printf("connect faield with error: %d\n", WSAGetLastError());
closesocket(ConnecSocket);
WSACleanup();
return 1;
}
第5、6步的发送和接收消息同服务器端的第7、8步
最终的服务器端、客户端的运行效果如下: