网络编程:基于TCP搭建一个网络聊天室

服务器端代码主要实现以下功能:

  1. 创建并绑定一个套接字,监听来自客户端的连接请求。
  2. 获取来自客户端的消息,并将该消息广播给所有已连接的客户端。
  3. 利用互斥量保护共享资源,防止多个线程同时修改此资源导致数据竞争问题。
  4. 创建一个独立的线程处理每个已连接的客户端,以便在服务器并发处理多个客户端请求时能够提高效率。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define MAX_CLIENTS 100
#define MAX_MSG_LEN 1024

#define PORT 5555  //1024-49151
#define IP "192.168.8.113" //本机ip地址 用ifconfig查看


struct message {
    char username[50];
    char content[MAX_MSG_LEN];
};

int clients[MAX_CLIENTS];
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;

void send_message(struct message msg, int self) 
{
    pthread_mutex_lock(&clients_mutex);
    for (int i = 0; i < MAX_CLIENTS; i++)
	{
        if (clients[i] && i != self) {
            send(clients[i], &msg, sizeof(msg), 0);
        }
    }
    pthread_mutex_unlock(&clients_mutex);
}

void *handle_client(void *arg) 
{
	int client_fd = (int)(intptr_t)arg;
    struct message msg;
    recv(client_fd, &msg, sizeof(msg), 0);
    printf("%s 加入聊天室\n", msg.username);

    while (1)
	{
        if (recv(client_fd, &msg, sizeof(msg), 0) <= 0) 
		{
            pthread_mutex_lock(&clients_mutex);
            for (int i = 0; i < MAX_CLIENTS; i++)
			{
                if (clients[i] == client_fd)
				{
                    printf("%s 离开聊天室\n", msg.username);
                    clients[i] = 0;
                    break;
                }
            }
            pthread_mutex_unlock(&clients_mutex);
            break;
        } else {
            printf("%s: %s\n", msg.username, msg.content);
            send_message(msg, client_fd);
        }
    }

    close(client_fd);
}

int main(int argc, char *argv[]) 
{
    int server_fd, client_fd, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    memset(clients, 0, MAX_CLIENTS * sizeof(int));

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) 
	{
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
	{
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(IP);   
	address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) 
	{
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 3) < 0) 
	{
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    printf("服务器开启端口号: %d\n", PORT);
    printf("等待客户端连接...\n");

    while (1) {
        if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) 
		{
            perror("accept failed");
            exit(EXIT_FAILURE);
        }

        int i;
        for (i = 0; i < MAX_CLIENTS; i++) 
		{
            if (clients[i] == 0) 
			{
                clients[i] = client_fd;
                break;
            }
        }

        if (i == MAX_CLIENTS) {
            printf("客户端连接失败\n");
            close(client_fd);
            continue;
        }

        pthread_t new_thread;
		if (pthread_create(&new_thread, NULL, handle_client, (void *)(intptr_t)client_fd) != 0)
		{
            printf("线程创建失败\n");
            continue;
        }
    }

    return 0;
}

 

客户端端代码主要实现以下功能:

  1. 创建并连接到服务器套接字。
  2. 接收来自其他客户端的消息并将其输出到终端。
  3. 向服务器发送该客户端所发送的消息,以便与其他客户端进行交流。
  4. 创建一个独立的线程接收来自服务器的消息并将其输出到终端,以避免阻塞 UI 线程。

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define MAX_USERNAME_LEN 50
#define MAX_MSG_LEN 1024

#define PORT 5555  //1024-49151
#define IP "192.168.8.113" //本机ip地址 用ifconfig查看

struct message
{
    char username[MAX_USERNAME_LEN];
    char content[MAX_MSG_LEN];
};

void *recv_message(void *arg) 
{
    int sockfd = *(int *)arg;
    struct message msg;

    while (1) 
	{
        ssize_t len = recv(sockfd, &msg, sizeof(msg), 0);
        if (len <= 0)
		{
            fprintf(stderr, "错误: 服务器断开连接 \n");
            break;
        } else if (len != sizeof(msg)) {
            fprintf(stderr, "错误: 接收消息长度不符 \n");
            continue;
        } else {
            printf("%s: %s\n", msg.username, msg.content);
        }
    }

    close(sockfd); // 关闭套接字
    exit(0);
}

int main(int argc, char *argv[]) 
{
  
    int sockfd;
    struct sockaddr_in serv_addr;
    char username[MAX_USERNAME_LEN];
    char password[MAX_USERNAME_LEN];
    struct message msg;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
	{
        fprintf(stderr, "错误: 套接字创建失败 \n");
        exit(1);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);


    if (inet_pton(AF_INET, IP, &serv_addr.sin_addr) <= 0) 
	{
        fprintf(stderr, "错误: 无效地址 \n");
        exit(1);
    }

    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 
	{
        fprintf(stderr, "错误: 连接服务器失败 \n");
        exit(1);
    }

    printf("请输入账户: ");
    fgets(username, MAX_USERNAME_LEN, stdin);
    username[strlen(username) - 1] = '\0';
    printf("请输入密码: ");
    fgets(password, MAX_USERNAME_LEN, stdin);
    password[strlen(password) - 1] = '\0';

    // 对用户名和密码进行验证

    strcpy(msg.username, username);
    if (send(sockfd, &msg, sizeof(msg), 0) == -1) 
	{
        fprintf(stderr, "错误: 发送消息失败 \n");
        close(sockfd); // 关闭套接字
        exit(1);
    }

    pthread_t thread_id;
    pthread_create(&thread_id, NULL, recv_message, (void *)&sockfd);

    while (1) 
	{
        printf("发送消息: ");
        fgets(msg.content, MAX_MSG_LEN, stdin);
        msg.content[strlen(msg.content) - 1] = '\0';

        if (send(sockfd, &msg, sizeof(msg), 0) == -1) 
		{
            fprintf(stderr, "错误: 发送消息失败 \n");
            break;
        }
    }

    pthread_cancel(thread_id);
    close(sockfd);

    return 0;
}

代码运行结果示例:

 

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、实验目的 1.掌握通信规范的制定及实现。 2.练习较复杂的网络编程,能够把协议设计思想应用到现实应用中。 二、实验内容和要求 1.进一步熟悉VC++6编程环境; 2.利用VC++6进行较复杂的网络编程,完成网络聊天室的设计及编写; 三、实验(设计)仪器设备和材料 1.计算机及操作系统:PC机,Windows; 2.网络环境:可以访问互联网; 四、 TCP/IP程序设计基础 基于TCP/IP的通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。设计思路(VC6.0下): 第一部分 服务器端 一、创建服务器套接字(create)。 二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。 三、接受来自用户端的连接请求(accept)。 四、开始数据传输(send/receive)。 五、关闭套接字(closesocket)。 第二部分 客户端 一、创建客户套接字(create)。 二、与远程服务器进行连接(connect),如被接受则创建接收进程。 三、开始数据传输(send/receive)。 四、关闭套接字(closesocket)。 CSocket的编程步骤:(注意我们一定要在创建MFC程序第二步的时候选上Windows Socket选项,其中ServerSocket是服务器端用到的,ClientSocket是客户端用的。) (1)构造CSocket对象,如下例: CSocket ServerSocket; CSocket ClientSocket; (2)CSocket对象的Create函数用来创建Windows Socket,Create()函数会自行调用Bind()函数将此Socket绑定到指定的地址上面。如下例: ServerSocket.Create(823); //服务器端需要指定一个端口号,我们用823。 ClientSocket.Create(); //客户端不用指定端口号。 (3)现在已经创建完基本的Socket对象了,现在我们来启动它,对于服务器端,我们需要这个Socket不停的监听是否有来自于网络上的连接请求,如下例: ServerSocket.Listen(5);//参数5是表示我们的待处理Socket队列中最多能有几个Socket。 (4)对于客户端我们就要实行连接了,具体实现如下例: ClientSocket.Connect(CString SerAddress,Unsinged int SerPort);//其中SerAddress是服务器的IP地址,SerPort是端口号。 (5)服务器是怎么来接受这份连接的呢?它会进一步调用Accept(ReceiveSocket)来接收它,而此时服务器端还须建立一个新的CSocket对象,用它来和客户端进行交流。如下例: CSocket ReceiveSocket; ServerSocket.Accept(ReceiveSocket); (6)如果想在两个程序之间接收或发送信息,MFC也提供了相应的函数。如下例: ServerSocket.Receive(String,Buffer); //String是你要发送的字符串,Buffer是发送字符串的缓冲区大小。ServerSocket.Send(String,Butter);//String是你要接收的字符串,Buffer是接收字符串的缓冲区大小。
### 回答1: 基于TCP通信的简单网络聊天室是一种通过TCP协议实现的实时聊天程序。该程序的实现涉及客户端和服务器端两个部分。 客户端和服务器端之间的通信依赖于TCP套接字编程。客户端首先与服务器建立连接,然后可以发送消息和接收其他客户端发送的消息。服务器端负责接收来自客户端的消息,并将这些消息广播给其他连接到服务器的客户端。 在实现这个聊天室时,需要考虑以下几个关键方面: 1. 建立连接 客户端通过指定服务器的IP地址和端口号来建立与服务器的连接。服务器端需要监听来自客户端的连接请求,并为每个客户端创建一个新的线程进行处理。 2. 消息传递 一旦建立连接,客户端和服务器可以通过套接字进行双向通信。客户端可以发送消息给服务器端,服务器端收到消息后会将其广播给所有连接到服务器的客户端。 3. 多线程处理 为了支持多个客户端同时与服务器进行通信,服务器端需要使用多线程处理来自不同客户端的消息。每当有新的客户端连接到服务器时,服务器就会为该客户端创建一个新的线程来处理它的消息。 4. 用户界面 虽然这个聊天室是基于网络的,但是对于用户来说,他们需要一个用户友好的界面来发送和接收消息。因此,在实现这个聊天室时,可以考虑设计一个简单的图形用户界面或者使用命令行界面。 总而言之,基于TCP通信的简单网络聊天室通过建立连接、消息传递、多线程处理和用户界面等关键步骤,实现了客户端之间的实时通信。这个聊天室可以作为教学实验或者业余项目,帮助学习者理解网络编程TCP通信的基本原理和应用。 ### 回答2: 基于TCP通信的简单网络聊天室一个允许多个用户通过网络进行实时聊天的应用程序。在实践网络编程实训时,我们可以按照以下步骤来创建一个简单的网络聊天室: 1. 创建服务器:首先,我们需要创建一个服务器端程序,它监听指定的端口,并接受客户端的连接请求。服务器使用套接字(socket)来进行通信。一旦有客户端连接,服务器接受该连接,并为每个客户端创建一个新的线程来处理通信。 2. 创建客户端:同时,我们还需要创建一个客户端程序,该程序用于连接服务器并进行通信。当客户端启动时,它会尝试连接到服务器的IP地址和端口。 3. 实现聊天功能:一旦客户端连接到服务器,它就可以发送和接收消息。服务器负责将接收到的消息广播给所有连接的客户端,以实现聊天功能。客户端可以向服务器发送消息,并显示其他客户端发送的消息。 4. 异常处理:在编程过程中,我们应该注意处理可能出现的异常,例如连接断开、网络故障等。在客户端和服务器端都应该实现适当的异常处理机制,以确保程序的稳定性。 5. 用户界面设计:除了实现通信功能,我们还可以考虑设计更友好的用户界面。客户端可以提供登录功能,允许用户输入用户名和密码。对于服务器端,我们可以设计管理功能,如踢出用户、禁言等。 通过完成以上步骤,我们可以实现一个基于TCP通信的简单网络聊天室。通过这个实训项目,我们可以学习网络编程的基本知识和技能,并了解网络应用的实现原理。同时,我们也可以通过此项目进一步加深对Python编程语言的了解和应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值