精华内容
下载资源
问答
  • MFC用CSocket写简易聊天软件
    2021-05-21 07:17:52

    CSocket通信原理:TCP/IP

    服务器端

    1.创建一个socket套接字(用于监听)

    2.获取服务器IP(本地IP)IP和端口端口(大于1024的端口)

    3.开始监听

    4.监听到有客户端连接服务器之后,创建一个新的socket套接字(用于通讯),用于和客户端之间的通信(继续监听)

    5.用新的socket套接字和客户端进行通信

    6.send();//发送消息

    Receive();//接收消息

    创建->绑定端口->监听->创建通信的socket->通信->关闭服务器

    |

    监听

    客户端

    1.创建一个socket套接字

    2.根据服务器的IP地址和通信端口连接服务器

    3.通过已经创建的socket套接字和客户端进行通信

    4.send();//发送消息

    Receive();//接收消息

    5.关闭客户端 close();

    创建->连接服务器->通信->关闭

    UDP型套接字

    服务器端(接收端)程序:

    1、创建套接字(socket)。

    2、将套接字绑定到一个本地地址和端口上(bind)。

    3、等待接收数据(recvfrom)。

    4、关闭套接字。 客户端(发送端)程序:

    1、创建套接字(socket)。

    2、向服务器发送数据(sendto)。

    3、关闭套接字。

    服务器端代码:

    #include #include void main()

    {

    WORD wVersionRequested;

    WSADATA wsaData;

    int err;

    wVersionRequested = MAKEWORD( 1, 1 );

    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 ) { return; }

    if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )

    {

    WSACleanup( );

    return;

    }

    SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);

    SOCKADDR_IN addrSrv;

    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);

    addrSrv.sin_family=AF_INET;

    addrSrv.sin_port=htons(6000);

    bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

    SOCKADDR_IN addrClient;

    int len=sizeof(SOCKADDR);

    char recvBuf[100];

    recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);

    printf("%s\n",recvBuf);

    closesocket(sockSrv); WSACleanup();

    }

    客户端代码:

    #include #include #include #pragma comment(lib,"WS2_32.lib")

    void main()

    {

    WORD wVersionRequested;

    WSADATA wsaData;

    int err;

    wVersionRequested = MAKEWORD(1, 1);

    err = WSAStartup(wVersionRequested, &wsaData);

    if (err != 0) { return; }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)

    {

    WSACleanup();

    return;

    }

    SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);

    SOCKADDR_IN addrSrv;

    InetPton(AF_INET,"127.0.0.1", (void *)&addrSrv.sin_addr.S_un.S_addr);

    addrSrv.sin_family = AF_INET;

    addrSrv.sin_port = htons(6000);

    sendto(sockClient, "Hello", strlen("Hello") + 1, 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    closesocket(sockClient);

    WSACleanup();

    }

    q1:无法解析的外部符号

    a1:在include后加一行#pragma comment(lib,“WS2_32.lib”)

    q2:error C4996: ‘inet_addr’

    a2:1.项目->属性->C/C+±>常规->SDL检查,将“是”改为“否”

    2.使用新函数InetPton()(客户端里面有使用实例)

    更多相关内容
  • 漫画C语言 做个聊天软件你不懂也得懂

    千次阅读 多人点赞 2021-12-02 12:58:49
    漫画C语言讲解实现socket聊天软件制作。

    学完C语言做不出东西?不存在的,咱们做一个最“隐私”的聊天器,就俩人,你和我。咱们聊天的信息你知我知没别人知。

    对了,本文评论区点赞、收藏抽奖。
    社区也有抽奖,本周社区抽奖帖子 :https://bbs.csdn.net/topics/603458199
    以上两个抽奖都是周日开奖,名额十个,然后书、日历等奖品都有。.
    没学过C语言的,觉得难的看这里:https://blog.csdn.net/a757291228/category_11468001.html

    我们直接开始写代码,只要你会基础的C语言,不要担心看不懂,不懂的我帮你刨根问底,把根都挖出来嚼烂,绝对懂。
    在这里插入图片描述

    一、一个聊天软件的基础模型是怎么样的?

    你是个新手的话你可能就会问,什么是模型?!听不懂,我在骗你学习。放心,我现在就告诉你什么是基础“模型”。

    我们可以简单的理解“模型”指这个聊天软件基本是怎么进行通信的,常规形式是怎样的,只要清楚了这个形式流程,然后在这个流程中添加一些代码就ok了,啥都不用想。如果你还是不懂什么是“流程”,那我就跟你说这个是一个步骤,只需要懂这个步骤,我们使用代码编写这个步骤就可以完成了。

    好了,现在没啥问题了吧?现在开始,第一步在一个通信中,一般有一个服务端。那什么是服务端?

    1.1 什么是服务端

    服务端就简单了,曾经…曾经…你去例如移动或者联通的营业挺,客服小姐姐就会对你提供服务,例如业务办理,办个卡,销个号等,那我们的服务端是用来通信的,所以这个服务端就是指等待跟我聊天的人,只要你上线了,开电脑打开软件了,连接上我的服务端了,咱们就可以聊天了。

    服务端一般就是一直在这里等你上线的那个,风里雨里我在这里等你。

    1.2 又不懂什么是客户端了?

    不懂没关系,打游戏懂吧?你下载到你电脑你手机的就是客户端,你打个游戏如果没有服务端就不能跟人匹配,这个懂了吧?

    1.3 基本的工具要拿过来吧?

    还知道头文件吧?
    头文件就等于是一个工具箱,需要干啥就可以使用拿头文件过来,这样就可以用里面的工具了。
    那咱们做一个聊天的软件就需要一个工具箱吧,这个工具箱叫做“winsock2.h”,那怎么拿呢?都知道#include<> 吧?
    那就直接把这个头文件拿过来就好了,代码就可以写成:#include<winsock2.h>
    常规的输入输出工具箱也要拿吧?所以就第一步把 stdio.h 也拿过来,所以这个服务端的第一行第二行代码就写成:

    #include<stdio.h>
    #include<WinSock2.h>
    

    1.4 开始 socket 编程

    不会了不会了!是不是一说 socket 你就说这是个什么鬼?
    我先说一句让你懵逼的定义“socket 就是应用之间通信的端点”。懂不懂?
    不懂呀,那我继续说。
    socket 就是两个通信软件之间的接口,你可以当成服务端是“插座”,客户端是“插头”,一插,欧了!这样不就通电了,这样说你明白了吧?
    当然这样解释比较片面,但用“抽象”的方式讲又不一定能让大家听得懂,所以你就理解成插头肯定没问题。
    在这里插入图片描述

    1.5 开始抬杠我拿三座插两座插不进!


    咱们用的插头都是有标准的,你想想,没有标准怎么那么多电器都可以用常规的插头?
    像这个 socket 这个通信端口,是有基于一些标准的。例如 TCP/IP这些通信协议。
    好了,我说了TCP/IP可能就会有同学问,这又是什么鬼!没关系,你只需要知道这个是一个通信协议,咱们现在是用 socket 进行通信就好,知道 socket 怎么用就行,协议咱们可不需要现在搞懂,咱们只需要知道 socket 如何运用即可。

    二、开始敲服务端代码

    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 类型数据的对象。
    什么鬼?又是 WSADATA 又是对象的,听不懂啊!
    没关系的拉,WSADATA 其实就是一个结构体,咱们在把使用socket的工具箱 WinSock2 拿过来的时候这个 WSADATA 结构体就已经创建好了,直接使用这个结构体创建一个结构体变量就好了。

    WSADATA 的作用就是用来存储初始化信息的,就像你打个游戏初始化创建一个人,这个人总得有信息吧,光头、小眼睛、腿短…对吧?

    那么我们的代码就可以写成以下:

    #include<stdio.h>
    #include<WinSock2.h>
    #include <stdlib.h>
    int main(){
    	WSADATA wsaData;
    }
    

    接下来就可以开始初始化了,初始化 socket 有一个函数叫做 WSAStartup,既然是函数一般都有参数吧,参数有哪些呢?
    这个 WSAStartup 方法需要传入一个 版本号,还有一个用于存储信息的 WSADATA 结构体。现在我们已经知道 WSADATA 的结构体就是上面这个代码创建的 wsaData 结构体变量,那么版本号又是什么?

    这个版本号是说明我们使用哪个 Winsock 版本,Winsock 有一个 1.1 版本还有一个 2.2 版本。两个版本有不同,1.1 版本只支持 TCP/IP 协议,还有一个版本 2.2 支持多个协议,这个时候你懂用哪个了吧?

    什么?! 还不懂? 那肯定是全都要呀!
    2.2 版本兼容性之类的更好,兼容啥我们不管,反正用多的。
    那直接写成 WSAStartup(2, 2, &wsaData)

    不不不,我们写法有一些不同,需要用一个函数 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

    这一步超级简单,代码就是这个:

    SOCKET serverSock = socket(PF_INET, SOCK_STREAM, 0);
    

    我知道你要骂我,写什么是什么鬼。
    好了好了,首先 SOCKET 是一个socket的类型,还记得 int a 吧?int 是一个类型,那么 SOCKET 肯定就是一个类型了,说明创建一个 SOCKET 类型的变量,然后 socket() 是创建 socket 的函数,这个没毛病吧?

    你说是里面的参数不懂?

    小问题了,第一个 PF_INET 就表示指定 IPV4 ,也就是说先给个网络协议,那么多的网络协议你总要选一个吧。那为什么要用 IPv4 呢?我只能说用这个东西计算更快,毕竟咱们做个聊天软件是局域网通信,你就理解为,咱们做的东西是个“小东西”,没必要那么大“体量”,迷你更好用,那就用那个 IPV4 了,你想不开你也可以用 IPV6 试试。

    那 SOCK_STREAM 是什么?SOCK_STREAM 表示咱们进行的通信是 TCP 通信,稳定可靠。在这里使用 SOCK_STREAM 也表示向我们的系统,或者你理解成“计算机”申请一个通信的端口,不然系统不给你“开个口子”,我的数据怎么传出去对吧,不然就是叫破喉咙都没人理我。

    那最后一个参数 0 又是什么呢?
    这里就是一个编号,说仔细点这个是 socket 所使用的传输协议编号,是不是不明白?其实这就是一个编号,不做设置,但是要给一个值,所以就给一个 0 咯。

    2.3 第三步绑定信息

    绑定信息这一步就有点玄了。在这里咱们要了解两个结构体,一个是 sockaddr_in,还有一个是 SOCKADDR。需要注意的是,这两个结构体包含的数据都是一样的,是一样的…

    主要是使用上有区别。有啥区别?
    sockaddr 是个系统用,而 sockaddr 是用来强制转换 sockaddr_in 结构体给系统调用的函数用。是不是迷茫?不要迷茫,一般都是这样做,那就这样做吧。你只需要记住,sockaddr 保存信息然后就别管了,而sockaddr 咱们就用来给参数给函数用。

    在 socket 中,咱们使用 sockaddr_in 结构体绑定监听的 IP 信息,首先需要创建这个结构体:

    struct sockaddr_in sockAddr;
    

    接下来始绑定端口、IP类型,其中 127.0.0.1 表示本机、1234 表示监听端口:

    sockAddr.sin_family = PF_INET; //IPv4
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器的IP
    sockAddr.sin_port = htons(1234); //端口
    

    这个懂没懂?
    sockAddr.sin_family 是表示这个结构体中用于存储IP协议的结构体变量,PF_INET 之前说了是 ipV4,表示在这里设置 ipV4类型。

    sockAddr.sin_addr.s_addr 这里是表示需要绑定的 ip 地址,在这里使用 inet_addr(“127.0.0.1”) 进行指定。那为什么指定个 ip 还需要 inet_addr?
    inet_addr 的作用是将一个字符串格式的ip地址转换成一个uint32_t数字格式。为什么要转换?那肯定是因为 sockAddr.sin_addr.s_addr 是一个 uint32_t 这个类型了。

    最后的 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, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    

    在这里 bind() 方法就是表示绑定信息了,第一个参数是 serverSock 就是表示要绑定的 socket,然后 (SOCKADDR*)&sockAddr 就是需要绑定的地址,最后一个就是一个地址长度。

    (SOCKADDR*)&sockAddr 我们讲过,SOCKADDR 就是给函数使用的,sockAddr 就是给系统使用的,所以就这样写就没毛病了。

    2.4 监听端口

    先让你懵一下,下面是代码:

    listen(serverSock, 20);
    

    简单吧?listen 就是表示监听,第一个参数就是要监听的 socket 第二个就是表示 同时能处理的最大连接。终于简单了这一步,你爽我也爽,还不懂就看下面漫画。

    2.5 有人请求聊天?设置个接待员

    接下来就是有人请求给你聊天了,那怎么办呢?一个人忙不过来呢,那就设置个接待员。

    SOCKADDR cIntAddr; 
    int nSize = sizeof(SOCKADDR);
    SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);
    

    accept 函数就是一个接待员,有人连接来敲门了,就需要去接待,换句比较专业的话就是 accept 接收一个套接字中已建立的连接。

    传入的参数第一个 serverSock 就是一个已连接的套接字,(SOCKADDR*)&cIntAddr 是一个按照规定的指向struct sockaddr的指针,所以我猜在前面创建,最后一个就是所指向这个指针的长度咯。

    设置完后就等于创建了一个接待员 cIntSock 。
    不过要注意,accept 没有连接的时候就会一直在等待,不然不会执行下面的代码的。


    这一部分的代码如下:

    #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) {


    那循环里面写啥?
    当然是写你接收信息和发送信息的代码了,我一次性贴上,简简单单:

    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够简单了吧?
    接着使用 send函数发送信息就可以了,第一个就是告诉接待员 cIntSock 要传达话了,sendBuf 就是咱们要说的话,第三个参数就是咱们说的话的长度,最后一个依旧是0,不用管。

    这样就还差最后一步就完成服务端了,此时咱们只需要关闭套接字就可以了,最后还需要清理一下,完整代码如下了:

    #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;
        sockAddr.sin_addr.s_addr = htons(INADDR_ANY);
        sockAddr.sin_port = htons(1234);
        bind(serverSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    
        listen(serverSock, 20);
    
        SOCKADDR cIntAddr; 
        int nSize = sizeof(SOCKADDR);
        SOCKET cIntSock = accept(serverSock, (SOCKADDR*)&cIntAddr, &nSize);
    
        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);
            send(cIntSock, sendBuf, strlen(sendBuf) + 1, 0);
        }
    	//关闭
        closesocket(cIntSock);
        closesocket(serverSock);
        WSACleanup();
        return 0;
    }
    

    三、客户端编写

    客户端和服务端是一样的你信吗?
    下面是代码:

    #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 连接服务器就没了,难道你说不是吗?
    简简单单对吧?那就行,解决。

    下面是演示示例:

    注意 若使用devc复制代码都报错,则点击编译->编译选项:
    在这里插入图片描述
    随后在出现的窗口中添加如下参数:

    展开全文
  • 大家好,我是KookNut39,在CSDN写文,分享一些自己认为在学习过程中比较重要的东西,致力于帮助初学者入门,希望可以帮助你进步。最近在更新C/C++方面的知识,感兴趣的欢迎关注博主,可以去专栏查看之前的文章,希望...

    大家好,我是KookNut39,在CSDN写文,分享一些自己认为在学习过程中比较重要的东西,致力于帮助初学者入门,希望可以帮助你进步。最近在更新C/C++方面的知识,感兴趣的欢迎关注博主,可以去专栏查看之前的文章,希望未来能和大家共同探讨技术。

    对于搞计算机的人来说,我们一直处在一个被外行深深误解的环境下,我还记得上学时候就发生过这样的对话,这位误会我的人还是我的好兄弟:

    韩:“白哥,今天用空吗?我电脑坏了,帮我看一下!”
    我:“我不太会修电脑啊!你重启试试”
    韩:“啊?你不是计算机专业吗?”
    我:“计算机科学与技术也不是计算机修理专业啊…”
    有的计算机专业的大佬可能真的会修计算机,也有的不是计算机的大佬,也会修计算机,反正,我不太会修电脑,我只会…重启试试???🤣🤣🤣

    为了不让往日悲剧在各位身上重现,各位还是写个程序给周围人看一看!告诉他们我们的专业性!那学习了这么久的C语言基础,不得用C语言来干点啥?写个简易的聊天软件,出去在同学面前装一下呗!众所周知,写代码如果不是为了在别人面前装个x,那将变得毫无意义!如果觉得文章不错,麻烦给个一键三连支持一下🤞🤞🤞,您的支持,是我最大的创作动力!

    废话够多了,赶紧进入正题!

    我们要实现一个简易的聊天系统,那最起码是两个人交互的,那么肯定是需要两个不同的端,也就是最起码两个程序,来实现这个简单的功能,我们就暂且理解为需要一个服务器端,一个客户端,这样让两个程序完成交互!该代码在Windows系统进行测试,如果需要在linux端实现,需要稍作改动!

    一、服务器

    首先来看服务器端,先来搞定几个头文件,不然其中的一些库函数会没法调用:

    #pragma once
    #include<WinSock2.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include<Windows.h>//必须在<WinSock2.h>的下面包含,否则编译不通过
    #pragma comment(lib,"WS2_32.lib")//要包含WinSock2.h必须要包这个库
    

    头文件中的这些库那都是必须要包含的内容,不然之后函数的调用就会出现一堆的报错,下来我们看一下main函数:

    	//初始化套接字类库 
    	//WSAStartup函数用于初始化Ws2_32.dll动态链接库。
    	//在使用套接字函数之前,一定要初始化Ws2_32.dll动态链接库 
    	WSADATA WsaData = { 0 };
    	if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0)
    	{
    		return;
    	}
    
    	// 创建监听套接字
    	SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if (ListenSocket == INVALID_SOCKET)
    	{
    		printf("Failed socket() \n");
    		return;
    	}
    

    第一件事情就是初始化套接字类库,因为我们需要利用套接字来完成进程间通信,所以类库肯定是要首先初始化的,接下来是创建一个监听套接字,在创建监听套接字的时候需要注意,socket函数中传的参数是非常关键的:

    SOCKET WSAAPI socket(
      _In_ int af,//地址家族规范,在这里我们传的是AF_INET 这是IPv4协议规范
      _In_ int type,//这个参数我们传递SOCK_STREAM,可靠的数据流传输,因为TCP协议
      _In_ int protocol//传输控制协议,用的TCP
    );
    

    这个函数的三个参数在TCP/IP通信中,基本是固定搭配套餐!当我们把监听套接字创建出来之后,需要将接听套接字与端口绑定:

    	// 填充sockaddr_in结构
    	struct sockaddr_in ServerAddress;
    	ServerAddress.sin_family = AF_INET;//Ipv4协议家族
    	ServerAddress.sin_port = htons(4567);   //端口号
    	ServerAddress.sin_addr.S_un.S_addr = INADDR_ANY;//客户端是本地地址
    	// 绑定套接字
    	if (bind(ListenSocket, (LPSOCKADDR)&ServerAddress, sizeof(ServerAddress)) == SOCKET_ERROR)
    	{
    		printf("Failed bind() \n");
    		return;
    	}
    

    上面的代码中,有一个结构体sockaddr_in其中包含了三个成员,有地址协议家族、监听端口号和监听的地址。其中端口号是随便设置的,只要在端口号范围之内,不要和知名端口号重复就行,我随便写了个4567,保证客户端也连接到这个端口就行!
    bind函数是绑定套接字和sockaddr_in结构体,为了让这个套接字可以在该端口和地址协议规范下完成监听,bind函数将本地地址与套接字关联起来。

    服务器端完成了套接字端口绑定之后,就要开始监听,listen函数将套接字置于侦听传入连接的状态。可以设置最大的连接数,在这里我随便设置了2。

    	// 进入监听模式  监听队列  最大连接数设置为 2
    	if (listen(ListenSocket, 2) == SOCKET_ERROR)
    	{
    		printf("Failed listen() \n");
    		return;
    	}
    

    那监听上线之后,就等着客户端的连接过来,需要一个叫做accept的函数来接受客户端的连接,accept函数允许对套接字的传入连接尝试。在这里设计算是偷了个懒,本应该弄一个循环,因为这是尝试连接,如果连接达到上限,就不允许其它的客户端接入了,应该不断尝试连接。但是这里我们主要为了讲一下实现原理,用于间单的测试还是没问题的

    
    
    	//用于接受客户端连接的IP地址等信息
    	struct sockaddr_in ClientAddress;
    	int AddressLength = sizeof(ClientAddress);//计算这个长度在accept处使用
    	
    	SOCKET ClientSocket;
    	printf("等待客户端连接:\n");
    	// 接受一个新连接
    	ClientSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddress, &AddressLength);
    	if (ClientSocket == INVALID_SOCKET)
    	{
    		printf("Failed accept()");
    	}
    

    客户端和服务器连接成功之后,我们创建一个线程,在线程创建过程中,把客户端的Socket当作参数传递给线程,这个线程用于给客户端发送消息:

    	printf("接收到连接:%s \r\n", inet_ntoa(ClientAddress.sin_addr));
    
    	HANDLE ThreadHandle = CreateThread(NULL,
    		0,
    		(LPTHREAD_START_ROUTINE)ThreadProcedure,
    		&ClientSocket,
    		0,
    		NULL);
    	if (ThreadHandle == NULL)
    	{
    		return 0;
    	}
    

    在这个回调线程执行函数中,用于和客户端通信,用gets来读取数据,遇到回车读取结束,然后只要保持连接,就可以一直给客户端发送消息,如果想断开连接,输入Over即可。

    //相当于一个发送消息模块
    DWORD WINAPI ThreadProcedure(LPVOID Parameter)
    {
    	SOCKET ClientSocket;
    	char BufferData[260];//最大发送的字符数
    	ClientSocket = *(SOCKET*)Parameter;
    	printf("You can speak now:\n");
    	while (1)
    	{
    		memset(BufferData, 0, sizeof(BufferData));
    		gets(BufferData);
    		// 向客户端发送数据
    		send(ClientSocket, BufferData, strlen(BufferData), 0);
    		if (!strncmp(BufferData, "Over", strlen("Over")))
    		{
    			// 关闭同客户端的连接 退出程序
    			closesocket(ClientSocket);
    			exit(0);
    		}
    	}
    	return 0;
    }
    

    在异步线程可以发送消息的同时,主线程也没闲着,它在接收客户端的数据发送,也是在一个while循环中,一直接受者来自客户端的消息,直到客户端发出Over指示,断开连接:

    	//用于接收数据
    	char BufferData[260];
    	while (TRUE)
    	{
    		memset(BufferData, 0, sizeof(BufferData));
    		recv(ClientSocket, BufferData, sizeof(BufferData), 0);
    		if (!strncmp(BufferData, "Over", strlen("Over")))
    		{
    			CloseHandle(ThreadHandle);
    			ThreadHandle = NULL;
    			break;
    		}
    		printf("Client Said: %s\n", BufferData);
    
    	}
    

    到这里,一个简单的服务器端就搞定了!!!接下来我们看一下客户端的实现吧:

    二、客户端

    客户端的代码实现逻辑其实和服务器端是相当接近的,我们需要包含的头文件也没有变化:

    #pragma once
    #include<WinSock2.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include<Windows.h>//必须在<WinSock2.h>的下面包含,否则编译不通过
    #pragma comment(lib,"WS2_32.lib")//要包含WinSock2.h必须要包这个库
    

    这些头文件都是必须包含的,在之前就已经说过了,因为实现逻辑很接近,所以我就找那些不太一样的地方来给大家解释一下:

    一上来那肯定是main函数了,里面还是一样,初始化类库,创建套接字:

    	//初始化套接字类库 
    	//WSAStartup函数用于初始化Ws2_32.dll动态链接库。在使用套接字函数之前,一定要初始化Ws2_32.dll动态链接库 
    	WSADATA v1 = { 0 };
    	if (WSAStartup(MAKEWORD(2, 2), &v1) != 0)
    	{
    		return;
    	}
    
    	// 创建套接字
    	SOCKET CommunicateSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if (CommunicateSocket == INVALID_SOCKET)
    	{
    		printf(" Failed socket() \n");
    		return;
    	}
    

    然后我们需要声明并且给sockaddr_in结构体赋值,这里有所不同,对于地址协议家族和端口号来说是一样的,尤其端口号,肯定要和服务器保持一致,然后我们讲连接的地址写为“127.0.0.1”,这是连接到本地的IP地址,在本机方便测试:

    // 填写远程地址信息
    	struct sockaddr_in ServerAddress;
    	ServerAddress.sin_family = AF_INET;
    	ServerAddress.sin_port = htons(4567);
    	//此处直接使用127.0.0.1即可 就是连接到本机
    	ServerAddress.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
    

    初始化结束之后,我们使用connect将客户端的套接字通过这个IP和端口来和服务器进行连接,connect函数建立到指定套接字的连接:

    	if (connect(CommunicateSocket, (SOCKADDR*)&ServerAddress, sizeof(ServerAddress)) == SOCKET_ERROR)
    	{
    		printf(" Failed connect() \n");
    		return;
    	}
    	if (CommunicateSocket == INVALID_SOCKET)
    	{
    		printf("Failed accept()");
    	}
    

    一旦连接成功,继续创建线程,这个线程传的是当前与服务器连接起来的CommunicateSocket,这个套接字就是客户端和服务器交流的桥梁:

    	printf("连接成功!!\r\n");
    
    	HANDLE ThreadHandle = CreateThread(NULL,
    		0,
    		(LPTHREAD_START_ROUTINE)ThreadProcedure,
    		&CommunicateSocket,
    		0,
    		NULL);
    	if (ThreadHandle == NULL)
    	{
    		return 0;
    	}
    

    创建异步线程还是一样的,将当前传入的套接字,用于给服务器发送消息,发送Over来结束当前会话:

    //向服务器发送消息
    DWORD WINAPI ThreadProcedure(LPVOID Parameter)
    {
    	SOCKET ServerSocket;
    	char BufferData[260];
    	ServerSocket = *(SOCKET*)Parameter;
    	printf("You can speak now:\n");
    	while (1)
    	{
    		memset(BufferData, 0, sizeof(BufferData));
    		gets(BufferData);
    		// 向服务器发送数据
    		send(ServerSocket, BufferData, strlen(BufferData), 0);
    		if (!strncmp(BufferData, "Over", strlen("Over")))
    		{
    			// 关闭同服务器的连接 退出程序
    			closesocket(ServerSocket);
    			exit(0);
    		}
    	}
    	return 0;
    }
    

    那main函数中的主线程肯定还是接受来自服务器端的消息,除非遇到Over指令,来结束对话:

    	//接受来自服务器的消息
    	char BufferData[260];
    	while (TRUE)
    	{
    		memset(BufferData, 0, sizeof(BufferData));
    		recv(CommunicateSocket, BufferData, sizeof(BufferData), 0);
    		if (!strncmp(BufferData, "Over", strlen("Over")))
    		{
    			CloseHandle(ThreadHandle);
    			ThreadHandle = NULL;
    			break;
    		}
    		printf("Server Said: %s\n", BufferData);
    
    	}
    

    到这里,实现就基本结束了,记得代码中断开连接之后,最后关闭套接字。代码记得首先启动服务器,这样才能达到监听的效果,让客户端顺利连接,我们来用动态图演示一下效果:
    在这里插入图片描述

    如果觉得文章不错,麻烦给个点赞+评论+收藏支持一下🤞🤞🤞。代码实现其实在文章中已经够详细了,但是如果有需要源码的,可以找我要。感谢您的阅读!

    今日份与君共勉:“人生如逆旅,我亦是行人”
    在这里插入图片描述

    展开全文
  • 使用C语言写的一个双人聊天程序,注释详细。是在Dev-C++5.11下编译的,双方聊天时,均要连接服务器。服务器在双方连接成功后,会对双方的消息进行互相转发。服务端负责转发消息,客户端会有一个独立的线程接受来自...
  • C语言实现简易聊天

    2021-05-17 08:19:43
    C语言聊天室基于 tcp 实现群聊功能,本项目设计是在「windows环境下基于套接字(Socket)和多线程编程」进行开发的「简易聊天室」,实现了群聊功能,在VC6.0和VS2019运行测试无误。运行效果聊天室分析设计Windows下...

    C语言聊天室

    基于 tcp 实现群聊功能,本项目设计是在「windows环境下基于套接字(Socket)和多线程编程」进行开发的「简易聊天室」,实现了群聊功能,在VC6.0和VS2019运行测试无误。

    运行效果207463636_1_20201115024840227聊天室

    分析设计

    Windows下基于windows网络接口Winsock的通信步骤为「WSAStartup 进行初始化」--> 「socket 创建套接字」--> 「bind 绑定」--> 「listen 监听」--> 「connect 连接」--> 「accept 接收请求」--> 「send/recv 发送或接收数据」--> 「closesocket 关闭 socket」--> 「WSACleanup 最终关闭」。207463636_2_20201115024840399通信流程

    了解完了一个 socket 的基本步骤后我们了解一下多线程以及线程的同步。

    多线程

    线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源。一个进程内的所有线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程。「简而言之多线程是为了提高系统的运行效率。」

    Win32 API下的多线程编程 也就是两个函数的应用创建线程CreateThread以及等待线程结束waitForSingleObject,具体案例这里不多做介绍。

    线程的同步

    每个线程都可以访问进程中的公共变量,资源,所以「使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性」。数据之间的相互制约包括

    1、「直接制约关系」,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称之为同步关系

    2、「间接制约关系」,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步

    windows线程间的同步方式有四种:「临界区、互斥量、信号量、事件。」

    本项目是基于事件内核对象实现的线程同步,事件内核对象是一种抽象的对象,有受信和未授信两种状态,通过等待WaitForSingleObject实现线程同步。事件内核对象的使用流程如下:

    「创建事件内核对象」

    HANDLE CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性

    BOOL                  bManualReset,  //是否手动重置事件对象为未受信对象

    BOOL                  bInitialState,  //指定事件对象创建时的初始状态

    LPCSTR                lpName    //事件对象的名称

    );

    「设置内核对象状态」BOOL SetEvent(

    HANDLE hEvent /*设置事件内核对象受信*/

    );

    BOOL ResetEvent(

    HANDLE hEvent /*设置事件内核对象未受信*/

    );

    「堵塞等待事件内核对象直到事件内核对象的状态为受信」

    DWORD WaitForSingleObject(

    HANDLE hHandle,

    DWORD  dwMilliseconds

    );

    具体使用阅读全文在我的个人网站里看,篇幅太多。

    服务端设计

    在创建套接字绑定监听之后会有一个等待连接的过程,在接收到新连接之后,需要创建一个线程来处理新连接,当有多个新连接时可通过创建多个线程来处理新连接,

    「定义最大连接数量以及最大套接字和最大线程」#define MAX_CLNT 256

    int clnt_cnt = 0;   //统计套接字

    int clnt_socks[MAX_CLNT]; //管理套接字

    HANDLE hThread[MAX_CLNT]; //管理线程

    「当有新连接来临的时候创建线程处理新连接」,并将新连接添加到套接字数组里面管理

    hThread[clnt_cnt] = CreateThread(

    NULL,  // 默认安全属性

    NULL,  // 默认堆栈大小

    ThreadProc, // 线程入口地址(执行线程的函数)

    (void*)&clnt_sock,  // 传给函数的参数

    0,  // 指定线程立即运行

    &dwThreadId); // 返回线程的ID号

    clnt_socks[clnt_cnt++] = clnt_sock;

    线程的处理函数ThreadProc不做过多讲解,大致就是「一个服务器,多个客户端进行数据的接收以及群发」。207463636_3_20201115024840524

    主要讲解「线程同步」,当有多个新连接来临的时候,可能会造成多个线程同时访问同一个数据(例如clnt_cnt)。这个时候就需要线程的同步来避免破坏数据的完整性。

    首先是「创建一个内核事件」HANDLE g_hEvent;   /*事件内核对象*/

    // 创建一个自动重置的(auto-reset events),受信的(signaled)事件内核对象

    g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);

    然后再需要访问连接数量clnt_cnt这个变量之前进行「加锁(设置等待)」,访问完成之后「解锁(设置受信)」

    /*等待内核事件对象状态受信*/

    WaitForSingleObject(g_hEvent, INFINITE);

    hThread[clnt_cnt] = CreateThread(NULL,NULL,ThreadProc,(void*)&clnt_sock,0,&dwThreadId);

    clnt_socks[clnt_cnt++] = clnt_sock;

    SetEvent(g_hEvent);    /*设置受信*/

    通过套接字数组来进行数据的转发实现群聊功能,此时也用到了「线程同步」void send_msg(char* msg, int len){

    int i;

    /*等待内核事件对象状态受信*/

    WaitForSingleObject(g_hEvent, INFINITE);

    for (i = 0; i 

    send(clnt_socks[i], msg, len, 0);

    SetEvent(g_hEvent);  /*设置受信*/

    }

    客户端设计

    同样也是在创建套接字连接到服务器之后,创建两个线程,一个线程实现数据的发送,一个实现数据的接收。

    「发送数据到服务端」

    DWORD WINAPI send_msg(LPVOID lpParam){

    int sock = *((int*)lpParam);

    char name_msg[NAME_SIZE + BUF_SIZE];

    while (1)

    {

    fgets(msg, BUF_SIZE, stdin);

    if (!strcmp(msg, 'q\n') || !strcmp(msg, 'Q\n'))

    {

    closesocket(sock);

    exit(0);

    }

    sprintf(name_msg, '[%s]: %s', name, msg);

    int nRecv = send(sock, name_msg, strlen(name_msg), 0);

    }

    return NULL;

    }

    「接收服务端数据并打印输出到显示器」DWORD WINAPI recv_msg(LPVOID lpParam){

    int sock = *((int*)lpParam);

    char name_msg[NAME_SIZE + BUF_SIZE];

    int str_len;

    while (1)

    {

    str_len = recv(sock, name_msg, NAME_SIZE + BUF_SIZE - 1, 0);

    if (str_len == -1)

    return -1;

    name_msg[str_len] = 0;

    fputs(name_msg, stdout);

    }

    return NULL;

    }

    这样就不会阻塞等待终端输入之后再显示服务端发送过来的消息了。

    遇到的问题

    等待线程返回的过程中最先用的是WaitForSingleObject,很遗憾这是个阻塞函数,直到线程返回才会继续往下执行,所以后面通过WaitForMultipleObjects这个windowsAPI调用对hThread线程数组进行线程等待释放。

    「缺陷:非高并发,对资源的利用不高,下周介绍Linux网络编程实现的聊天室,可能有新功能,敬请期待..」

    整个过程不算太难,主要是仅实现了群聊功能,所以只需要了解windows下的网络编程以及多线程编程和线程的同步方法就可以实现这个样一个功能。

    「源代码后台发送关键字windows聊天室获取」

    socket网络编程方法可参考上期

    多线程以及线程的同步可通过「阅读全文」在我的个人网站里面查阅。

    展开全文
  • 2、启动客户端后,与服务器的连接建立成功,两者界面如下图: 3、连接建立成功后,开始传输字符串聊天,结果如下: 网络聊天实验完成 标签:addr,int,函数,C语言,hi,socket,服务器,hello,客户端 来源: ...
  • 如何用C语言做一个简单的服务器和客户端,实现一个聊天室程序呢?这里就简单的写一下博主的实现。一、程序需求实现一个简单的服务器,包括以下功能:可以监听并且与多个客户端建立TCP链接。可以接收客户端发来的消息...
  • C语言写这个还是挺费时间的,因为什么功能函数都要自己写,不像C++有STL库可以用,MFC写就更简单了,接下来我还会更新MFC版本的多人聊天程序。好了,废话少说,进入主题。这个程序要解决的问题如下:1.CPU使用率...
  • 可以关注我加我好友提你们想学的内容哟~ 目录 《看聊天记录都学不会C语言?太菜了吧》(22)(必懂!题解 1-100 内素数)素数原来是质数!为什么你不早说!——(必懂!题解)求素数 《看聊天记录都学不会C语言?太...
  • c语言聊天

    2012-02-20 20:49:49
    用c编写的聊天软件,简单易学,适合于初学者学习之用!
  • C语言实现Linux聊天工具

    千次阅读 2019-01-08 11:46:13
    C语言实现Linux聊天工具。
  • C语言项目——聊天室(一)

    千次阅读 2019-02-13 15:03:59
    makefile的好处就是——“自动化编译” ,一旦写好makefile文件,只需要一个make命令,整个工程就可以自动完成编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,...
  • 该楼层疑似违规已被系统折叠隐藏此楼查看此楼#include"winsock.h"//##include"stdio.h"//# #pragma comment(lib,"ws2_32.lib");//#int main(){//#char Sendbuf[100];//#char Receivebuf[100];//#int SendLen;...
  • socket即时网络通信工具,服务端客户端都有,详细注释
  • C语言来实现一个局域网内类似于腾讯qq的聊天工具。
  • 不仅仅是C语言。 你不必读特别的教程。 可以在扁豆中查找相关信息,提高C语言...找到C语言的即时通讯软件的源代码并学习socket编程广讯通协同办公平台。插座,它实现了哪些功能? 电子邮件或聊天跪下! ! 开元代...
  • 做自己的UDP聊天软件

    2021-05-22 05:38:05
    主要控件:WinSock.ocx '第三方控件,只有企业版才有,精简版没有TextBox2个CommandBox1个本例中我无法给大家爱展现的点对点通讯效果,因为我把两个地址都填入的是自己的,所以看上去像是跟自己聊天,但是原理和思路...
  • C语言利用串口实现两套电脑的聊天,可以在DOS里面运行,一台电脑需要用串口助手软件接收数据,另一台利用DOS运行程序,通过串口连接两台电脑
  • linux下的简单聊天室源代码(c语言实现),功能比较简单,适合初学linux网络编程的同学参考
  • Linux网络编程:用C语言实现的聊天程序(同步通信)[参考].pdf
  • C语言构建网络聊天

    2010-07-09 15:08:51
    Winsock是90年代初,为了方便网络编程,由Microsoft联合了其他几家公司共同制定的一套WINDOWS下的网络编程接口,它是通过C语言的动态链接库方式提供给用户及软件开发者的,主要由winsock.h头文件和动态链接库winsock...
  • 计算机二级C语言考试题库软件

    千次阅读 2021-11-02 19:51:16
    顺手分享:计算机二级C语言考试题库软件 对于没有相应基础的绝大多数同学,如果想考过二级,首先要克服见到陌生复杂题目的恐惧感。话说回来,其实这些题没有哪一道是真正需要动脑进行复杂思考的,只是知识点没见过...
  • SendMsg(_T(" 进入聊天室")); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } void CChatClientDlg::ProcessPendingRead(void) { char buffer[200]; memset(buffer, '\0', 200); // 这里解决了乱码问题 ...
  • 局域网聊天软件

    2018-07-19 23:40:43
    基于MFC的socket编程设计的局域网聊天软件,仅有C语言的基础,本人初次尝试Visual C++6.0平台上使用C++进行Windows程序设计的网络编程,对C++、MFC只是初步认识,在此基础上查找各种网络资源和文本资源初次尝试,...
  • C语言能干什么?手把手教你写一个简单的聊天软件

    千次阅读 多人点赞 2021-06-24 11:23:14
    一、服务端代码 因为端口号容易被占用的原因,所以IP地址和端口... //将从服务器读到的数据打印 } } } 这样一个简单的聊天软件就写好了,虽然没有页面,但是实现了服务器和客户端通过网络进行通信的功能, 三、运行结果
  • 《用C语言实现基于TCP协议的网络通讯》由会员分享,可在线阅读,更多相关《用C语言实现基于TCP协议的网络通讯(6页珍藏版)》请在人人文库网上搜索。1、用C#实现基于TCP协议的网络通讯TCP协议是一个基本的网络协议,...
  • socket实例C语言:一个简单的聊天程序

    万次阅读 多人点赞 2018-10-10 14:49:23
    我们老师让写一个简单的聊天软件,并且实现不同机子之间的通信,我用的是SOCKET编程。不废话多说了,先附上代码: 服务器端server.c #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #...
  • windows环境下C语言多线程实现网络编程,多人聊天室,[总结].pdf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,252
精华内容 3,700
关键字:

c语言聊天软件