在Linux下基于TCP网络通信的多人在线聊天室

一、使用原理

TCP网络传输;多线程事件处理;

 

二、功能简介:(服务端)

1、打开服务端,服务端进行初始化,并等待客户端的连接;

2、打开客户端,输入服务端的IP地址与端口号;服务端会产生一个线程与新增的客户端进行通信,并分配客户端的名称,每连接一个客户端,服务端会产生一个线程与客户端进行通信;

3、每新建一个客户端服务端会为客户端分配一个名字,依次为: ‘a’,'b','c',.....

三、使用方法

1、单独发送信息:每个客户端发送的信息会保存在服务端的缓冲区中,例如

客户端a发送:

a hello;

即发送给自己消息:

hello;

客户端a发送:

b hello;

即发送给客户端b消息:

hello;

2、群发消息:

在发送的消息前面加上 ‘x’ ,即触发群发消息的功能;

 

四、编译   

由于使用了线程的功能,所以在编译的时候  gcc server.c -lpthead -o server  加上线程库;

 

五、代码(server.c)     客户端可以先使用      nc 127.0.0.1 9527    (服务端IP加端口号) 进行调试

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
//#include<ctype.h>
#include<unistd.h>
//#include<errno.h>
#include<pthread.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define SERV_PORT 9527          /*服务端的端口号       */
#define ONLINEMAX 10             /*同时在线的最大人数   */

void *ChatOnline(void *arg);    /*线程的聊天,消息处理函数   */
void fuck_filter(const char* buf);
int UserSelect(char name);

/*用于用户信息的临时参数存储 ->  套接字的文件描述符 && IP+PORT  */
struct arg_user
{
        int cfd;
        struct sockaddr_in clit_addr;
};

/*用户信息的存放函数 ->用户名 && 套接字文件描述符    */
struct user
{
        char  name;
        int user_cfd;
}clit_user[ONLINEMAX];

/*报错函数  */
void sys_err(const char *str)
{
        perror(str);
        exit(1);
}

static int user_number = 0;     /* 用户的号数*/

int main()
{
        int sfd = 0,cfd = 0;
        int ret = 0,i = 0,tmp = 0;
        char buf[BUFSIZ];        //BUFSIZ大小为8192字节
        struct sockaddr_in serv_addr,clit_addr;
        struct arg_user *arg;
        socklen_t clit_addr_len;
        pthread_t pthread;

        /*设置 struct sockaddr_in 里面的内容 采用IPv4 端口号为9527 IP号由INADDR_ANY生成的二进制的IP地址   */
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        /*生成套接字,并校验*/
        sfd = socket(AF_INET,SOCK_STREAM,0);
        if(sfd == -1){
                sys_err("socket error");
        }
        printf("socket .....");

        /*绑定端口号+IP地址  */
        bind(sfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
        printf("bind .......");

        /*设置监听的上限        */
        listen(sfd,20);
        printf("listen .....");

        int opt = SO_REUSEADDR;
        setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      /*setsockopt()用来设置参数s所指定的socket状态。
      参数level代表欲设置的网络层,一般设成SOL_SOCKET以存取socket层。
      参数optname代表欲设置的选项,有下列几种数值:SO_REUSEADDR 允许在bind()过程中本地地址可重复使用
        参数  optval代表欲设置的值,参数optlen则为optval的长度。
      返回值  成功则返回0,若有错误则返回-1,错误原因存于errno。*/
        /*监听,阻塞至有客户端连接,并返回一个新的套接字与客户端建立通讯 */

        clit_addr_len = sizeof(clit_addr);


        /*读取客户端发来的信息,并转化为大写    */
        while(1){
                printf("accept .....\n");
                cfd = accept(sfd,(struct sockaddr *)&clit_addr,&clit_addr_len);
                if(cfd == -1)   sys_err("accpet error");
//              printf("client's IP = %s",inet_ntoa(clit_addr.sin_addr));

/*指向结构体的结构体要先分配空间    将用户信息收集起来 */
                arg = malloc(sizeof(struct arg_user));
                arg->cfd = cfd;
                memcpy((void *)&arg->clit_addr, &clit_addr, sizeof(clit_addr));

                tmp = pthread_create(&pthread,NULL,ChatOnline,(void*)arg);
                if(tmp != 0)    sys_err("pthread_create faile");
        }

        close(sfd);
        close(cfd);
        return 0;
}

void *ChatOnline(void *arg)
{
        int cfd = 0;int ret = 0;
        struct sockaddr_in clit_addr;
        struct arg_user *n_arg;
        char buf[BUFSIZ];
//      char *p=NULL;
        char name;
        struct pid{
                int pid_pid;
                char pid_name;
        }my_pid;

        n_arg = malloc(sizeof(struct arg_user));
        n_arg = arg;
        cfd = n_arg->cfd;
        clit_addr = n_arg->clit_addr;

        clit_user[user_number].name = 'a'+user_number;
        clit_user[user_number].user_cfd = cfd;
        my_pid.pid_name = clit_user[user_number].name;
        if(user_number++ > ONLINEMAX) user_number = 0;

        printf("client's IP = %s...name = %c\n",inet_ntoa(clit_addr.sin_addr),my_pid.pid_name);
        my_pid.pid_pid = pthread_self();

        while(1){
                memset(buf,0,BUFSIZ);
                ret = read(cfd,buf,sizeof(buf));
                if(ret == 0 || ret == -1){
                        perror("client disconnec");
                        close(cfd);
                        break;
                }
                write(STDOUT_FILENO,buf,ret);

                fuck_filter(buf);       //话语过滤
                name = buf[0];

                if(name == 'x'){
                        int i;
                        for(i = 0;i < ONLINEMAX;i++){
                                if(clit_user[i].user_cfd > 0){
        //                              write(clit_user[i].user_cfd,"[THE MESSAGE From]:->",28);
        //                              write(clit_user[i].user_cfd,p,sizeof(p));
                                        write(clit_user[i].user_cfd,buf,ret);

                                }
                        }
                }
                int user_cfd = UserSelect(name);
                if(user_cfd > 0 ){
                        write(user_cfd,buf,ret);

                }
        }
}
int UserSelect(char name)
{
    int ufd = 0;
    int i;
    for(i=0;i<= ONLINEMAX;i++)
    {

        if(name == clit_user[i].name)
            return clit_user[i].user_cfd;
    }

    return ufd;
}


void fuck_filter(const char* buf)
{
    char buf_fuck[] = "fuck";
    char * p = NULL;
    do{
        p = strstr(buf,buf_fuck);
        if(p != 0){
            *p = '*';
            *(++p) = '*';
            *(++p) = '*';
            *(++p) = '*';
        }
    }while(p != 0);
}

 

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
设计思路: 1. 服务器端建立一个监听socket,等待客户端的连接请求; 2. 当客户端连接请求到达时,服务器端接受请求,创建一个新的socket与客户端进行通信,并将其加入到一个socket列表中保存; 3. 客户端连接成功后,发送一个登录请求到服务器端,服务器端接收到请求后将客户端的信息加入到一个客户端列表中保存; 4. 当某个客户端发送消息时,服务器端接收到消息后,将消息转发给所有在线客户端; 5. 当某个客户端断开连接时,服务器端将其从客户端列表和socket列表中删除,并且通知其他客户端该用户已下线。 实现步骤: 1. 服务器端创建监听socket,等待客户端连接请求; 2. 客户端连接成功后,服务器端创建一个新的socket与客户端进行通信,并将其加入到一个socket列表中保存; 3. 客户端连接成功后,发送一个登录请求到服务器端,服务器端接收到请求后将客户端的信息加入到一个客户端列表中保存; 4. 当某个客户端发送消息时,服务器端接收到消息后,将消息转发给所有在线客户端; 5. 当某个客户端断开连接时,服务器端将其从客户端列表和socket列表中删除,并且通知其他客户端该用户已下线。 代码实现: 1. 服务器端 ```python import socket def server(): # 创建socket对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定IP地址和端口号 server_socket.bind(('127.0.0.1', 8000)) # 监听端口 server_socket.listen(5) # 客户端列表 clients = [] # 循环等待客户端连接 while True: client_socket, client_addr = server_socket.accept() print(f'客户端{client_addr}连接成功!') # 将新客户端加入到客户端列表中 clients.append((client_socket, client_addr)) # 创建一个新线程,处理该客户端的消息 threading.Thread(target=handle_client, args=(client_socket, clients)).start() def handle_client(client_socket, clients): # 接收客户端发送的登录请求 username = client_socket.recv(1024).decode() # 将客户端信息加入到客户端列表中 clients.append((client_socket, username)) # 向所有在线客户端广播新用户加入的消息 broadcast(f'{username}已加入聊天室!', clients) # 循环接收客户端发送的消息 while True: try: message = client_socket.recv(1024).decode() # 将消息转发给所有在线客户端 broadcast(f'{username}: {message}', clients) except: # 当客户端断开连接时,从客户端列表和socket列表中删除该客户端 clients.remove((client_socket, username)) broadcast(f'{username}已离开聊天室!', clients) break def broadcast(message, clients): # 循环向所有在线客户端发送消息 for client in clients: client_socket = client[0] # 发送消息时,需要将消息转换为字节流 client_socket.send(message.encode()) if __name__ == '__main__': server() ``` 2. 客户端 ```python import socket import threading def client(): # 创建socket对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务器 client_socket.connect(('127.0.0.1', 8000)) # 发送登录请求 username = input('请输入您的用户名:') client_socket.send(username.encode()) # 创建一个新线程,用于接收服务器发送的消息 threading.Thread(target=receive_message, args=(client_socket,)).start() # 循环发送消息 while True: message = input() client_socket.send(message.encode()) def receive_message(client_socket): while True: message = client_socket.recv(1024).decode() print(message) if __name__ == '__main__': client() ``` 以上代码实现了简单的TCP通信的多人聊天室,但仅供参考,具体实现还需要考虑更多的细节问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值