首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Linux Socket 編程:實現雙向通信的簡易聊天程序(Server/Client 完整代碼)

     編輯:程式設計綜合問答

需求背景

在 Linux 環境下使用 Socket 編寫類似 QQ 的簡易聊天程序,要求雙方能循環收發消息。原始代碼存在「單次收發後斷開」問題,需改造為持久連接並支持雙向交互。

核心問題分析

  • Server 端 `accept` 後僅 `read`/`write` 一次即 `close(newfd)`,導致無法持續通信。
  • Client 端 `while(1)` 中重複 `connect`,應在連接成功後進入收發循環。
  • 缺乏退出機制,建議約定特定字符(如 `.`)結束會話。

改進版 Server 代碼

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

#define BUF_SIZE 1024
#define LISTEN_PORT 8889
#define LISTEN_NO 10
#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

int sock_make(void);

int main(void) {
    int sockfd, connfd;
    char recvbuf[BUF_SIZE], sendbuf[BUF_SIZE];
    int n;

    sockfd = sock_make();
    printf("Server listening on port %d...\n", LISTEN_PORT);

    while (1) {
        connfd = accept(sockfd, NULL, NULL);
        if (connfd < 0) err_exit("accept");

        while (1) {
            // 接收客戶端消息
            memset(recvbuf, 0, BUF_SIZE);
            n = read(connfd, recvbuf, BUF_SIZE - 1);
            if (n <= 0) break; // 連接斷開或出錯
            printf("Client: %s", recvbuf);
            if (strncmp(recvbuf, ".", 1) == 0) break; // 收到 '.' 結束

            // 發送回覆
            printf("Server: ");
            fgets(sendbuf, BUF_SIZE, stdin);
            write(connfd, sendbuf, strlen(sendbuf));
            if (strncmp(sendbuf, ".", 1) == 0) break;
        }
        close(connfd);
    }
    close(sockfd);
    return 0;
}

int sock_make(void) {
    int sockfd;
    struct sockaddr_in servaddr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) err_exit("socket");

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(LISTEN_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        err_exit("bind");
    if (listen(sockfd, LISTEN_NO) < 0)
        err_exit("listen");
    return sockfd;
}

改進版 Client 代碼

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

#define BUF_SIZE 1024
#define LISTEN_PORT 8889
#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <server_ip>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sockfd, n;
    struct sockaddr_in servaddr;
    char sendbuf[BUF_SIZE], recvbuf[BUF_SIZE];

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) err_exit("socket");

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(LISTEN_PORT);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
        err_exit("inet_pton");

    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        err_exit("connect");

    printf("Connected to server. Type '.' to quit.\n");

    while (1) {
        // 發送消息
        printf("Client: ");
        fgets(sendbuf, BUF_SIZE, stdin);
        write(sockfd, sendbuf, strlen(sendbuf));
        if (strncmp(sendbuf, ".", 1) == 0) break;

        // 接收回覆
        memset(recvbuf, 0, BUF_SIZE);
        n = read(sockfd, recvbuf, BUF_SIZE - 1);
        if (n <= 0) break;
        printf("Server: %s", recvbuf);
        if (strncmp(recvbuf, ".", 1) == 0) break;
    }
    close(sockfd);
    return 0;
}

運行示例

# Terminal 1 (Server)
$ ./server
Server listening on port 8889...
Client: hello
Server: hi there
Client: .
# Terminal 2 (Client)
$ ./client 127.0.0.1
Connected to server. Type '.' to quit.
Client: hello
Server: hi there
Client: .

關鍵改進點總結

  1. Server 單次 `accept` 後進入內層 `while(1)` 循環,持續收發直到雙方約定退出。
  2. Client 僅 `connect` 一次,之後在同一連接上循環讀寫。
  3. 使用 `fgets` 替代 `read(STDIN_FILENO)`,便於處理換行符。
  4. 約定 `.` 作為結束標記,雙方均可主動斷開。
  5. 增加錯誤處理宏 `err_exit`,代碼更健壯。

此版本實現了基本的雙向文本聊天功能,可作為進一步擴展(如多線程、非阻塞 I/O、圖形界面)的基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved