学完C语言做不出东西?不存在的,咱们做一个最“隐私”的聊天器,就俩人,你和我。咱们聊天的信息你知我知没别人知。 ![]() 我们直接开始写代码,只要你会基础的C语言,不要担心看不懂,不懂的我帮你刨根问底,把根都挖出来嚼烂,绝对懂。 ![]() 一、一个聊天软件的基础模型是怎么样的?你是个新手的话你可能就会问,什么是模型?!听不懂,我在骗你学习。放心,我现在就告诉你什么是基础“模型”。 我们可以简单的理解“模型”指这个聊天软件基本是怎么进行通信的,常规形式是怎样的,只要清楚了这个形式流程,然后在这个流程中添加一些代码就ok了,啥都不用想。如果你还是不懂什么是“流程”,那我就跟你说这个是一个步骤,只需要懂这个步骤,我们使用代码编写这个步骤就可以完成了。 好了,现在没啥问题了吧?现在开始,第一步在一个通信中,一般有一个服务端。那什么是服务端? 1.1 什么是服务端服务端就简单了,曾经…曾经…你去例如移动或者联通的营业挺,客服小姐姐就会对你提供服务,例如业务办理,办个卡,销个号等,那我们的服务端是用来通信的,所以这个服务端就是指等待跟我聊天的人,只要你上线了,开电脑打开软件了,连接上我的服务端了,咱们就可以聊天了。 服务端一般就是一直在这里等你上线的那个,风里雨里我在这里等你。 ![]() 1.2 又不懂什么是客户端了?不懂没关系,打游戏懂吧?你下载到你电脑你手机的就是客户端,你打个游戏如果没有服务端就不能跟人匹配,这个懂了吧? 1.3 基本的工具要拿过来吧?还知道头文件吧? #include<stdio.h> #include<WinSock2.h> 1.4 开始 socket 编程不会了不会了!是不是一说 socket 你就说这是个什么鬼? ![]() 1.5 开始抬杠我拿三座插两座插不进!咱们用的插头都是有标准的,你想想,没有标准怎么那么多电器都可以用常规的插头? 二、开始敲服务端代码2.1 搞清楚使用 socket 进行通信的步骤编写C语言Windows下的socket需要经过几个步骤:首先对WSAStartup 进行初始化,初始化对socket 套接字(socket也叫套接字)进行创建,随后配合绑定信息,接着进行配置信息的bind 绑定;绑定了信息后,通过该信息进行isten 监听,监听后若有链接则connect 连接,再接下来开始使用accept 接收请求,得到请求后可以选择接受recv或者send发送数据,最后closesocket 关闭 socket,WSACleanup 最终关闭。 简单点就是下面的这个流程: ![]() ![]() 不懂了?不懂就慢慢来嘛。 这是进行 socket 编程的步骤,如果你要问为什么要这样做…我只能回答你规定的流程就这样,因为你要进行通信,那肯定需要创建一个 socket ,创建完毕后那么肯定要绑定你要通信的信息,如果你不绑定你怎么知道你要跟谁说话呢?急着我收到了一个信息后就等于跟我请求通话,我同意了,咱们就开始通信了,通信肯定要发送信息,那就用send这些方法发送了,最后面说完话我就关闭这个 socket了,那你说不是吗? 还不懂?那你看下面。 ![]() 2.1 第一步初始化既然第一步是初始化,那我要初始化什么东西? WSADATA 的作用就是用来存储初始化信息的,就像你打个游戏初始化创建一个人,这个人总得有信息吧,光头、小眼睛、腿短…对吧? 那么我们的代码就可以写成以下:
接下来就可以开始初始化了,初始化 socket 有一个函数叫做 WSAStartup,既然是函数一般都有参数吧,参数有哪些呢? 这个版本号是说明我们使用哪个 Winsock 版本,Winsock 有一个 1.1 版本还有一个 2.2 版本。两个版本有不同,1.1 版本只支持 TCP/IP 协议,还有一个版本 2.2 支持多个协议,这个时候你懂用哪个了吧? 什么?! 还不懂? 那肯定是全都要呀! 不不不,我们写法有一些不同,需要用一个函数 MAKEWORD 对版本进行生成,就像这样 WSAStartup(MAKEWORD(2, 2), &wsadata);,规定咱们使用 MAKEWORD 告诉 WSAStartup 初始化调用什么版本。 ![]() 那么整个初始化的代码就如下所示咯: #include<stdio.h>#include<WinSock2.h>#include <stdlib.h>int main(){ WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsadata);} 什么?不懂 &wsadata ?来来来,我们的漫画同学告诉你是啥意思: ![]() 懂了吧?传个地址方便信息存储。 2.2 第二步创建 socket这一步超级简单,代码就是这个:
我知道你要骂我,写什么是什么鬼。 你说是里面的参数不懂? 小问题了,第一个 PF_INET 就表示指定 IPV4 ,也就是说先给个网络协议,那么多的网络协议你总要选一个吧。那为什么要用 IPv4 呢?我只能说用这个东西计算更快,毕竟咱们做个聊天软件是局域网通信,你就理解为,咱们做的东西是个“小东西”,没必要那么大“体量”,迷你更好用,那就用那个 IPV4 了,你想不开你也可以用 IPV6 试试。 那 SOCK_STREAM 是什么?SOCK_STREAM 表示咱们进行的通信是 TCP 通信,稳定可靠。在这里使用 SOCK_STREAM 也表示向我们的系统,或者你理解成“计算机”申请一个通信的端口,不然系统不给你“开个口子”,我的数据怎么传出去对吧,不然就是叫破喉咙都没人理我。 那最后一个参数 0 又是什么呢? ![]() 2.3 第三步绑定信息绑定信息这一步就有点玄了。在这里咱们要了解两个结构体,一个是 sockaddr_in,还有一个是 SOCKADDR。需要注意的是,这两个结构体包含的数据都是一样的,是一样的… 主要是使用上有区别。有啥区别? 在 socket 中,咱们使用 sockaddr_in 结构体绑定监听的 IP 信息,首先需要创建这个结构体: struct sockaddr_in sockAddr; 接下来始绑定端口、IP类型,其中 127.0.0.1 表示本机、1234 表示监听端口:
这个懂没懂? sockAddr.sin_addr.s_addr 这里是表示需要绑定的 ip 地址,在这里使用 inet_addr(“127.0.0.1”) 进行指定。那为什么指定个 ip 还需要 inet_addr? 最后的 sockAddr.sin_port 是表示要指定某一个端口,在这里指定 1234 这个端口。 所以该部分的代码就写成这样了: #include<stdio.h>#include<WinSock2.h>#include <stdlib.h>int main(){ WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsadata); SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in sockAddr; sockAddr.sin_family = PF_INET; //IPv4 sockAddr.sin_addr.s_addr = inet_addr('127.0.0.1'); //服务器的IP sockAddr.sin_port = htons(1234); //端口} 最后就是绑定一下了:
在这里 bind() 方法就是表示绑定信息了,第一个参数是 serverSock 就是表示要绑定的 socket,然后 (SOCKADDR*)&sockAddr 就是需要绑定的地址,最后一个就是一个地址长度。 (SOCKADDR*)&sockAddr 我们讲过,SOCKADDR 就是给函数使用的,sockAddr 就是给系统使用的,所以就这样写就没毛病了。 ![]() 2.4 监听端口先让你懵一下,下面是代码: listen(serverSock, 20); 简单吧?listen 就是表示监听,第一个参数就是要监听的 socket 第二个就是表示 同时能处理的最大连接。终于简单了这一步,你爽我也爽,还不懂就看下面漫画。 ![]() 2.5 有人请求聊天?设置个接待员接下来就是有人请求给你聊天了,那怎么办呢?一个人忙不过来呢,那就设置个接待员。
accept 函数就是一个接待员,有人连接来敲门了,就需要去接待,换句比较专业的话就是 accept 接收一个套接字中已建立的连接。 传入的参数第一个 serverSock 就是一个已连接的套接字,(SOCKADDR*)&cIntAddr 是一个按照规定的指向struct sockaddr的指针,所以我猜在前面创建,最后一个就是所指向这个指针的长度咯。 设置完后就等于创建了一个接待员 cIntSock 。 ![]() 这一部分的代码如下: #include<stdio.h>#include<WinSock2.h>#include <stdlib.h>int main(){ WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsadata); SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in sockAddr; sockAddr.sin_family = PF_INET; //IPv4 sockAddr.sin_addr.s_addr = inet_addr('127.0.0.1'); //服务器的IP sockAddr.sin_port = htons(1234); //端口 listen(serverSock, 20); SOCKADDR cIntAddr; int nSize = sizeof(SOCKADDR); SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);} 2.6 开始循环聊天在聊天的时候肯定是需要一个循环,不用循环只能发一次信息就完成了,所以肯定有一个 while:
![]() 那循环里面写啥? while (1) { char sendBuf[50]={'Hello client'}; char recvBuf[50]; recv(cIntSock, recvBuf, 50, 0); printf('来自客户端:'); printf('%s\n', recvBuf); printf_s('请输入内容:'); scanf('%s',sendBuf); //sendBuf='s'; //gets_s(sendBuf); send(cIntSock, sendBuf, strlen(sendBuf) + 1, 0); } sendBuf就是一个字符数组,用来输入自己的要输入的内容。 主要看recv,recv 接收4个参数,第一个参数是建立的通信、第二个参数是是一个数组,接收数据存放的地方、之后会缓存大小,最后一个参数是指定调用方式,不用管一般设置为0。 cIntSock 就是刚刚从套接字里接受的那个接待员,现在就用接待员和他说话了。 接着就使用printf显示接待员听到的话,简简单单。 然后就到我们输入信息,使用scanf够简单了吧? 这样就还差最后一步就完成服务端了,此时咱们只需要关闭套接字就可以了,最后还需要清理一下,完整代码如下了:
三、客户端编写客户端和服务端是一样的你信吗? #include<stdio.h>#include<winsock2.h>int main(){ WSADATA wsadata; int nRes = WSAStartup(MAKEWORD(2, 2), &wsadata); SOCKET sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in sockAddr; sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr('127.0.0.1'); //只需要在这里指向服务器 ip 就可以了 sockAddr.sin_port = htons(1234); //连接服务器 connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); while (1) { char recvBuf[50]; char sendBuf[50]={'Hello server'}; printf('跟服务端说: '); scanf('%s',sendBuf); send(sock, sendBuf, strlen(sendBuf) + 1, 0); recv(sock, recvBuf, 50, 0); printf('服务端跟你说: '); printf('%s\n', recvBuf); } closesocket(sock); WSACleanup(); system('pause');} 不同的几个点只有使用了 connect 连接服务器就没了,难道你说不是吗? 下面是演示示例: ![]()
![]()
![]() |
|