对于socket通讯,网上有很多教程,我就简单的说一下socket通讯步骤,便于理解下面的代码。
服务器端:
首先,应该初始化套接字库,初始化套接字socket()
然后,绑定socket套接字到一个端口上,bind()
接着,将套接字设为监听状态listen()
此时,可以用accept()接受连接请求,它也会返回一个socket套接字,这是对每个连接的套接字。
现在,可以使用rec(),send()进行消息的收发
最后,结束连接,关闭套接字closesocket(),对于当前连接的套接字和服务端的总套接字要分别关闭。清理套接字库。
客户端:
加载套接字库,创建套接字socket();
向服务器发出连接请求connect();
和服务器端进行通信send()/recv();
关闭套接字closesocket(),关闭加载的套接字库。
网络通信中,大多数情况应该有多个客户端,多个客户端同时连接服务端。可以有多种解决方案,比如IOCP模型。
这里我采用最简单的创建多线程,为每个连接创建一个线程,结束连接时关闭此线程。
按照网上说法,创建线程最好使用_beginthreadex而不要使用createthread。
服务端代码:
#include <stdio.h>
#include <winsock2.h>
#include <process.h>
#include <windows.h>
#include <assert.h>
#pragma comment(lib,"ws2_32.lib")
//参数结构体,用来把多个参数传入线程函数,
struct args{
SOCKET sClient; //当前连接的socket套接字
sockaddr_in remoteAddr; //当前连接的端口号
};
//每个连接的线程
unsigned int __stdcall createlink(void* arg){
args ap = *((args*)arg); //这里将传入的无类型指针转换成args*类型,再取其值所表示的结构体,赋值给ap
printf("接受到一个连接:%s:%d\r\n", inet_ntoa(ap.remoteAddr.sin_addr), ap.remoteAddr.sin_port);
char revData[255] = {};
while (1)
{
//接收数据
int ret = recv(ap.sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
if (revData[0] == '#')
{
printf("END\n");
break;
//如果接收到的字符串首字符为'#',跳出收发消息循环
}
}
//发送数据
//char * sendData = "服务端:你好,TCP客户端!\n";
char sendData[20];
sprintf(sendData,"ser: hello! %d!\n",ap.remoteAddr.sin_port);
send(ap.sClient, sendData, strlen(sendData), 0);
}
closesocket(ap.sClient); //结束连接前关闭当前连接的套接字
return 0;
}
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
printf("等待连接...\n");
int thrdnum = 0;
HANDLE handle[5]; //连接线程数组,最大5个连接
while(1){
//accept()获得接入连接,返回连接的套接字
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
SOCKET sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
}
args arg;
arg.sClient = sClient;
arg.remoteAddr = remoteAddr;
//为连接创建线程,传入连接套接字,端口号
handle[thrdnum++] = (HANDLE)_beginthreadex(NULL, 0, createlink, &arg, 0, NULL);
}
WaitForMultipleObjects(thrdnum, handle, TRUE, INFINITE); //等待所有线程都结束
closesocket(slisten); //关闭服务端的套接字
WSACleanup();
getchar();
return 0;
}
这里有一处需要特别注意,就是把args结构体作为参数传入线程参数,这里有两种实现:
一种是我现在使用的方法,在main中,while(1)表示一直等待连接接入,每次连接,就会把sClient结构体指针传进去。这里传进去的是一个void * 类型,传进去后转换成args*类型,再把内容赋值给新的args结构体ap。
另一种办法是把 main中声明args arg改成args arg = new args;然后传入arg,这样相当于为每个连接new一个结构体,不过要记得在结束连接时释放内存。
我之前采用了传的结构体指针,内部却忘了把它赋值给一个新的结构体,导致每次连接的信息会覆盖旧连接,导致旧连接卡死。
目前的程序没有写如何结束服务端的死循环,暂时就这样吧。
客户端代码:
#include <cstdio>
#include <winsock2.h>
#include<ctype.h>
//#pragma comment(lib,"ws2_32.lib")
int communicate(SOCKET sclient)
{
//通讯前进行一次应答,获得端口信息
char remote_num[7] = {};
int digit_locate =0;
char recData[255] = {};
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
for(int i = 0 ;recData[i] != ''; i++)
if(isdigit(recData[i]))
remote_num[digit_locate++]=recData[i];
}
char inputd[200] = {};
//进入收发循环
while(1) {
char sendData[255] = {};
sprintf(sendData,"%s: %s!\n", remote_num, inputd);
//发送信息
send(sclient, sendData, strlen(sendData), 0);
printf(sendData);
//接收信息
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
printf("please input: ");
scanf("%s",inputd);
//如果输入的首字母为'#'或'*',跳出循环
if(inputd[0] == '#' || inputd[0] == '*')
break;
}
send(sclient, inputd, strlen(inputd), 0);
return 0;
}
int main(int argc, char* argv[])
{
//初始化套接字
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
//连接服务器
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
//进行通讯函数
communicate(sclient);
//关闭套接字,结束客户端
closesocket(sclient);
WSACleanup();
printf("END\n");
getchar();
return 0;
}

支付宝打赏
微信打赏