![628ed2e0a0659438773acf3ed5b2a1a9.png](https://i-blog.csdnimg.cn/blog_migrate/2a1ae7060058b59874e4f1c354b266e9.png)
程序主要分为两部分: 套接字通讯的建立 和 线程(用thread)函数的编写和使用
主要是这两个东西都比较陌生,网上好多博客细节都不是很到位,中间纠结了好久。
程序主要流程:WSA初始化(win套接字环境)->初始化套接字->服务器绑定并监听->服务器接收客户端->创建一个thread与这个客户端通信,在任务函数里死循环接收和发送->继续等待接收 (客户端把绑定监听和接收变成 主动连接就行了)。
这里附上两段代码,一段是服务器的main,一段是服务器的运行函数:
int main() //main内即主线程
{
if(!WSA_init() || !Creat_Socket(sServer)||!Bind_A_Listen())// 初始化
return-1;
while(1)
{
if(Service_Run()) //服务器运行
Sleep(1000);
else {
printf("Sth wrong,bye...n");
break;}
}
WSACleanup();
system("pause");
}
//
bool Service_Run()
{
SOCKADDR_IN AcptClitAddr;
int len = sizeof(SOCKADDR);//TODO 零时变量怎么让子线程保存 赋值产生的socket需要关闭吗
sSrReSen[++ct]=accept(sServer,(SOCKADDR*)&AcptClitAddr,&len);
sockAcpt=sSrReSen[ct];
if(INVALID_SOCKET == sockAcpt)
{printf("Accept socket error: %dn",WSAGetLastError()); return false;}
else printf("Accept client IP:[%s]n", inet_ntoa(AcptClitAddr.sin_addr)); //错误打印错误信息,成功打印IP
//现在已经成功建立了链接,该使用分线程执行任务了
if(!Pthread_SET(120,PTHREAD_CREATE_DETACHED,Thread_Work,(void*)&ct))
return false;
return true;
}
其中thread的任务函数Threadz_Work() 主要都是字符串打印,注意一下子线程里静态变量和全局变量的使用就可以了,后面会提到.
最后的效果:时间是自己打印的。
![1ef1dd22c433f99afff6978c9980c12a.png](https://i-blog.csdnimg.cn/blog_migrate/dd6e4f5eaa79ffdeec299e1fec3dd844.jpeg)
我遇到的一些迷惑点:
1.重复调用回调函数时,其中的静态声明会串吗?
A:会串,看来静态变量和全局变量一样,不会储存在独立的堆栈里,但能不用尽量不用,要加锁容易出错。
堆与栈的区别_Dablelv的博客专栏-CSDN博客
2.赋值产生的SOCKET,需要手动关闭吗?
A:不用,SOCKET 本质上是unsigned longlong 储存的是句柄(也就是相当于只储存了地址,没储存内容信息,所以SOCKET的复制就和int复制一样,不用担心深拷贝之类的),只要销毁一一次,其他的SOCKET虽然有值,但已经无法操作了。
注意,句柄相当于是一个整数类型的指针,但跨越编译器和语言,操作系统级的,这是为了更好的使用一些通用功能(窗口、socket之类)。
C++ 什么是句柄?为什么会有句柄?HANDLE
3.在函数中声明的 SOCKET 传入独自循环的回调函数后,当函数运行结束,这个SOCKET还存在吗
A:存在,SOCKET没有储存内容信息,只是传递了句柄,因此SOCKET类型的变量的死活不会影响建立的SOCKET链接(就是地址和变量的关系),这个SOCKET本身不会消失,这是丢了指向的内容。
4.每个线程中都有延时sleep,甚至阻塞语句 accept recive 等时,他们是如何工作的。
A:之前写过一些嵌入式,里面用了UCOS系统,就是当程序有多个任务是,自动协调先后顺序,类似于见缝插针,本质上还是只有一个主线程。而thread各个线程都是完全独立并行的,并不是像嵌入式的操作系统那样间歇性交替,只要注意不要让它们抢夺调用资源形成锁就行。(即便对于单核的CPU来说本质上是串行的,但是底层会自动协调,不用自己考虑这个线程等待的时候那个线程要不要执行这种事情)
注意:sleep这种硬延时还是应该少用,C++里有可以获得线程执行状态的函数,可以减少资源浪费。
5.函数指针,回调函数与void* :void *(*workfun)(void *)?
A:
- void* 类型为void的指针,“void*能包容地接受各种类型的指针。也就是说,如果你期望接口能够接受任何类型的参数,你可以使用void*类型。但是在具体使用的时候,你必须转换为具体的指针类型。”(知乎),就好比寄快递,知道发货地址和收获地址,void*就像运货的,不管你给啥都给你传过去,但你要知道自己寄的是啥东西,送到那干嘛。
- workfun做为回调函数,一般难以确定传入和传处参数,故返回值和参数都是void*
- (*workfun)是函数指针定义时的表示方式,一定要加括号,前面的是返回类型,后面的是参数列表,函数指针定义必须两个都要有。
6.SOCKADDR_IN 的 协议族、端口和IP地址,为啥都一样都不会串?
A:虽然网上一会说可以一样一会说不能一样,但二者的究极目的就是为了区分链接的客户端,然而本程序用的client监听和accept函数获取的客户端,也就是说每次链接成果后都会得到指定的对应本次链接的SOCKET句柄,因此只要把它们分开保存好,服务器的端口和IP可以随便设置...(对于其他模式和工作方式应该端口和IP是有用的,我对TCP传输什么的还不太了解,这里TODO)。
注意:区分不同客户端的方法还有很多,比如两边按照一定的通讯协议进行身份验证。
剩下就是一些看上去令人迷惑的宏定义,多百度百度就好了
这里是完整的代码:
最后推荐俩博客 虽然不全但写的比较好:
Linux Socket编程(不限Linux)www.cnblogs.com![16672a53c9042b49b9da871cf6b49d35.png](https://i-blog.csdnimg.cn/blog_migrate/ecf3b505ccba48c62134e269bc673cc7.png)
![8dbec974f4bf35f9f845f87f9a05977f.png](https://i-blog.csdnimg.cn/blog_migrate/48f19cc855abb3b30641d95fe2ad2a51.png)