1. 程式人生 > >模擬QQ聊天——採用TCP協議的C/S架構實現

模擬QQ聊天——採用TCP協議的C/S架構實現

模擬QQ聊天,一個伺服器處理多個客戶端的連線,同時要求各個客戶端之間能夠自由通訊。
本程式採用C/S架構,利用多執行緒完成。

伺服器端:

a#include <stdio.h>
#include <sys/socket.h>      
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 8888 //設定埠號
struct info //定義結構體 { int ToFd; //指定傳送訊息給誰 char buf[100]; //訊息內容 }; /* 接收訊息 */ void *MyReceive(void *arg) { struct info RecvBuf; //定義接收訊息的緩衝區 int ret; pthread_detach(pthread_self()); //執行緒分離 while(1) { ret = recv(*(int *)arg, &RecvBuf, sizeof(RecvBuf), 0); //伺服器接收來著一個客戶端的訊息
if (-1 == ret) { perror("recv"); exit(1); } if (!strcmp(RecvBuf.buf, "bye")) //如果接收到bye { close(*(int *)arg); //關閉 break; } ret = send(RecvBuf.ToFd, &RecvBuf, sizeof(RecvBuf), 0); //伺服器轉發收到的訊息給指定的客戶端
if (-1 == ret) { perror("send"); exit(1); } memset(&RecvBuf, 0, sizeof(RecvBuf)); //清空緩衝區 } } int main() { int sockfd, ret, fd[100] = {0}, length, i = 0; pthread_t tid[100] = {0}; char buf[100] = {0}; struct sockaddr_in server_addr; //用於存放伺服器本身的資訊,包括自己的埠號和ID struct sockaddr_in client_addr; //接收客戶端連線的時候,用於存放客戶端資訊 printf("Start Server!\n"); sockfd = socket(PF_INET, SOCK_STREAM, 0); //建立socket,處理客戶端的連線,不用於傳送資訊 if (-1 == sockfd) { perror("socket"); exit(1); } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = PF_INET; //地址族 server_addr.sin_port = PORT; //指定socket的埠號 server_addr.sin_addr.s_addr = inet_addr("192.168.192.128"); //本機IP ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); //上述資訊繫結到socket if (-1 == ret) { perror("bind"); exit(1); } printf("Listening...\n"); ret = listen(sockfd, 5); //監聽,是否有客戶端發起連線 if (-1 == ret) { perror("listen"); exit(1); } while(1) { length = sizeof(client_addr); fd[i] = accept(sockfd,(struct sockaddr *)&client_addr, &length); //接受客戶端的連線,返回值用於傳送資訊 if (-1 == fd[i]) { perror("accept"); exit(1); } printf("Accept %d, Port %d\n", fd[i], client_addr.sin_port); ret = pthread_create(&tid[i], NULL, MyReceive, (void *)&fd[i]); //建立執行緒 if (0 != ret) { perror("pthread_create"); exit(1); } i++; } close(sockfd); return 0; }

客戶端:

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

#define PORT 8888   //設定埠號,與伺服器一致

struct info
{
    int ToFd;
    char buf[100];
};

pthread_t tid[2] = {0};

/* 傳送訊息 */
void *Send(void *arg)
{
    struct info SendBuf;
    int ret, oldtype;

    while(1)
    {
        scanf("%s %d",SendBuf.buf, &SendBuf.ToFd);  //輸入指定接收的客戶端以及訊息內容

        ret = send(*(int *)arg, &SendBuf, sizeof(SendBuf), 0);  //傳送
        if (-1 == ret)
        {
            perror("send");
            exit(1);
        }

    if (!strcmp("bye", SendBuf.buf))    //以bye結束
        {
            close(*(int *)arg);     //關閉該執行緒
            pthread_cancel(tid[1]); //同時取消該客戶端用於接收訊息的執行緒
            break;
        }
        memset(&SendBuf, 0, sizeof(SendBuf));
    }   
}

/* 接收訊息 */
void *Receive(void *arg)
{
    struct info RecvBuf;
    int ret, oldtype;

    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);   //設定執行緒取消的型別

    while(1)
    {
        ret = recv(*(int *)arg, &RecvBuf, sizeof(RecvBuf), 0);
        if (-1 == ret)
        {
            perror("Receive");
            exit(1);
        }

        printf("Receive : %s\n", RecvBuf.buf);

        memset(&RecvBuf, 0, sizeof(RecvBuf));
    }
}

int main()
{
    int sockfd, ret;
    char buf[100] = {0};
    struct sockaddr_in server_addr; //向server_addr發起連線

    sockfd = socket(PF_INET, SOCK_STREAM, 0);   //建立socket,既用於連線,又用於傳送資訊
    if(-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }
    memset(&server_addr, 0, sizeof(server_addr));   //都是伺服器資訊
    server_addr.sin_family = PF_INET;
    server_addr.sin_port = PORT;
    server_addr.sin_addr.s_addr = inet_addr("192.168.192.128");

    printf("Start Connecting...\n");
    ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));    //發起連線
    if (-1 == ret)
    {
        perror("connect");
        exit(1);
    }

    ret = pthread_create(&tid[0], NULL, Send, (void *)&sockfd);     //建立執行緒,用於傳送
    if (0 != ret)
    {
        perror("pthread_creat1");
        exit(1);
    }

    ret = pthread_create(&tid[1], NULL, Receive, (void *)&sockfd);  //建立執行緒,用於接收
    if (0 != ret)
    {
        perror("pthread_creat2");
        exit(1);
    }

    pthread_join(tid[0], NULL); //阻塞,不讓執行緒結束
    pthread_join(tid[1], NULL);
    close(sockfd);

    return 0;
}

這裡寫圖片描述

該程式還有些地方存在不足:
1.若一個客戶端向另一個已經下線的客戶端傳送訊息會出現混亂;
2.客戶端之間傳送訊息時,訊息內容不能還有含有空格,因為scanf輸入訊息內容和指定接收訊息物件時是以空格為分界;
3.若客戶端尚未關閉,直接關閉伺服器會發生混亂(不過這點好像並不算不足吧,畢竟實際上伺服器是長期開著的)

還有其他地方的不足 歡迎指出!

還有一點需要改進的地方:接收訊息的一方不知道傳送該訊息的客戶機(還請高手指教)