最近關於socket編程的理解

来源:http://www.cnblogs.com/newhere/archive/2016/08/18/5785147.html
-Advertisement-
Play Games

最近看了看socket網路編程,對於我這種一點經驗都沒有的選手來說只能理解一點點吧。所以在此記錄一下最近的收穫。 socket編程無非就那幾個函數,對於服務端來說,主要的為socket(),bind(),listen(),accept(),close()。對於客戶端來說主要為connect(),cl ...


  最近看了看socket網路編程,對於我這種一點經驗都沒有的選手來說只能理解一點點吧。所以在此記錄一下最近的收穫。

  socket編程無非就那幾個函數,對於服務端來說,主要的為socket(),bind(),listen(),accept(),close()。對於客戶端來說主要為connect(),close()等。當然,我所說的只是針對tcp協議而言的。對於udp而言,就可以簡單很多,服務端和客戶端都建立socket併進行綁定,從而用sendto()和recvfrom()通信即可。

  以下直接上一個關於tcp協議的客戶端和服務端的程式。

 

//此為服務端的程式
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <netdb.h> //當SIGCHLD信號出現時則執行此函數 //當子進程停止時,SIGCHLD信號會送給父進程,預設是忽略該信號 void sig_handler(int signo) { pid_t pid; int stat; //用WNOHANG參數如果沒有任何已終止的進程,它仍會立即返回,而不是像wait那樣永遠等下去 //waitpid如果成功的話返回子進程的pid,如果沒有子進程退出則返回0 pid = waitpid(-1, &stat, WNOHANG); while(pid > 0) { printf("child process terminated (PID: %ld)\n", (long)getpid()); pid = waitpid(-1, &stat, WNOHANG); } return 0; } int main(int argc, char *argv[]) { int listen_fd; int com_fd; int ret; int i; static char recv_buffer[1024]; int len; int port; pid_t pid; /* struct sockaddr { unsigned short sa_family; //address family, AF_xxx char sa_data[14]; //14 bytes of protocal address }; struct sockaddr_in { short int sin_family; //address family, AF_INET unsigned short int sin_port; //port number struct in_addr sin_addr; //internet address unsigned char sin_zero[8]; //same sizeas struct sockaddr }; struct in_addr { uint32_t s_addr; //4 bytes }; sockaddr是通用套接字地址,sockaddr_in是internet環境下套接字的地址形式,兩者 結構一樣,都為16個位元組。 */ struct sockaddr_in clt_addr; struct sockaddr_in srv_addr; //伺服器運行時要給出埠信息,該埠為監聽埠 if(argc != 2) { printf("Usage: %s port\n", argv[0]); return 1; } //atoi為ascii to integer,字元串轉換為整型 port = atoi(argv[1]); //設置處理信號函數 if(signal(SIGCHLD, sig_handler) < 0) { perror("cannot set the signal"); return 1; } //創建套接字用於伺服器的監聽 if((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("cannot creat listening socket"); return 1; } //將srv_addr全部置零,INADDR_ANY就是inet_addr("0.0.0.0"),表示不確定 //地址,也就是表示本機的所有IP memset(&srv_addr, 0, sizeof(srv_addr)); srv_addr.sin_family = AF_INET; srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); srv_addr.sin_port = htons(port); if((ret = bind(listen_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))) == -1) { perror("cannot bind server socket"); close(listen_fd); return 1; } //指定監聽埠,連接5個客戶端 if((ret = listen(listen_fd, 5)) == -1) { perror("cannot listen the client connect request"); close(listen_fd); return 1; } //對每個連接來的客戶端創建一個進程,單獨與其進行通信 //首先調用read函數讀取客戶端發送來的信息 //將其轉換成為大寫後發送回客戶端 //當輸入@時,程式退出 //用fork()函數的原因是保持伺服器能容許多個客戶端的連接 while(1) { len = sizeof(clt_addr); if((com_fd = accept(listen_fd, (struct sockaddr*)&clt_addr, &len)) < 0) { //EINTR表示系統調用被信號中斷 if(errno == EINTR) { continue; } else { perror("cannot accept client connect request"); close(listen_fd); return 1; } } pid = fork(); if(pid < 0) { perror("cannot create the child process"); close(listen_fd); return 1; } //當有客戶端連接的時候,在子進程中進行客戶端與服務端的通信 else if(pid == 0) { while((len = read(com_fd, recv_buffer, 1024)) > 0) { printf("Message from client(%d): %s\n", len, recv_buffer); if(recv_buffer[0] == '@') { break; } for(i = 0; i < len; i++) { recv_buffer[i] = toupper(recv_buffer[i]); } write(com_fd, recv_buffer,len); memset(recv_buffer, 0, 1024); } close(com_fd); return 0; } //父進程直接結束此次迴圈等待下一個客戶端進行連接 else { close(com_fd); } } return 0; }
//此為服務端的程式
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <netdb.h> #include <unistd.h> int main(int argc, char *argv[]) { int connect_fd; int ret; char snd_buffer[1024]; int i; int port; int len; static struct sockaddr_in srv_addr; if(argc != 3) { printf("Usage: %s server_ip_address port\n", argv[1]); return 1; } port = atoi(argv[2]); if((connect_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("cannot creat communication socket"); return 1; } memset(&srv_addr, 0, sizeof(srv_addr)); srv_addr.sin_family = AF_INET; srv_addr.sin_addr.s_addr = inet_addr(argv[1]); srv_addr.sin_port = htons(port); if((ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))) == -1) { perror("cannot connect to the server"); close(connect_fd); return 1; } //memset(snd_buffer, 0, 1024); while(1) { memset(snd_buffer, 0, 1024); write(STDOUT_FILENO, "input message: ", 15); len = read(STDIN_FILENO, snd_buffer, 1024); if(len > 0) write(connect_fd, snd_buffer, len); len = read(connect_fd, snd_buffer, len); if(len > 0) printf("Message from serverL %s\n", snd_buffer); if(snd_buffer[0] == '@') break; } close(connect_fd); return 0; }

  當然,這屬於老版本的socket程式,最新的socket編程推薦使用getaddrinfo()函數,這個函數是通過一個hints參數來當做地址的返回標準的。具體的getaddrinfo()函數如下:

struct addrinfo {
    int    ai_flags;            //input flags
    int    ai_family;            //address family
    int    ai_socktype;            //SOCK_STREAM, SOCK_DGRAM
    int    ai_protocol;            //socket protocol
    size_t ai_addrlen;            //size of structure pointed to by ai_addr    
    char  *ai_cannonname;        //canonical name of host
    struct sockaddr *ai_addr;    //pointer to socket address structure
    struct addrinfo *ai_next;    //next structure in linked list
};

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node,          //host name to connect to or an IP address
                const char *service,    //port number, /etc/services
                const struct addrinfo *hints,    //fill with relevant information, only ai_flags, ai_family, ai_socktype, ai_protocol
                struct addrinfo **res    
                );

  上面的服務端程式是通過fork()函數來實現容許多個客戶端進行連接的,下麵我要用select()函數來實現多個客戶端的連接。當然,也同時用了getaddrinfo()這種新特性。

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

#define PORT "1234"

void *get_in_addr(struct sockaddr *sa) {
    if(sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void) {
    fd_set    master;
    fd_set    read_fds;
    int     fdmax;

    int     listener;
    int     newfd;
    struct sockaddr_storage remoteaddr;
    socklen_t    addrlen;

    char    buf[256];
    int     len;

    char    remoteIP[INET6_ADDRSTRLEN];

    int     yes = 1;
    int     i, j, rv;
//    int     client[100], n;

    struct addrinfo hints, *ai, *p;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
        perror("getaddrinfo");
        return 1;
    }

    for(p = ai; p != NULL; p = p->ai_next) {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if(listener < 0) {
            continue;
        }

        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        if(bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
            close(listener);
            continue;
        }

        break;
    }

    if(p == NULL) {
        perror("socket and bind");
        return 2;
    }

    freeaddrinfo(ai);

    if(listen(listener, 10) == -1) {
        perror("listen");
        return 3;
    }

    FD_SET(listener, &master);

    fdmax = listener;

    while(1) {
        printf("HERE!!! BEFORE SELECT\n");
        read_fds = master;
        if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select");
            return 4;
        }

        printf("HERE!!! AFTER SELECT\n");

        for(i = 0; i <= fdmax; i++) {
            printf("round i is %d\n", i );
            if(FD_ISSET(i, &read_fds)) {
                printf("isset i is %d\n", i );
                if(i == listener) {
                    addrlen = sizeof(remoteaddr);
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
                    printf("listener is %d, newfd is %d,listener i is %d\n", listener, newfd, i);

                    if(newfd == -1) {
                        perror("accept");
                    }
                    else {
                        FD_SET(newfd, &master);
                        if(newfd > fdmax) {
                            fdmax = newfd;
                        }
                        printf("selectserver: new connection form %s on socket %d\n", 
                            inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd);
                    }
                }
                else {
//                    if(i != newfd) {
//                        printf("you are in other's zone\n");
//                        goto fail;
//                    }
                    newfd = i;                    //註意這步!!!很重要!!!
                    while(1) {
                        printf("in the newfd round\n");
                        if((len = read(newfd, buf, 256)) <= 0) { 
                            perror("read");
                            break;
                        }
                        printf("newfd i is %d\n", i);
//                    while((len = read(newfd, buf, 256)) > 0) {
                        printf("Message form client(%d): %s", len, buf);
                        if(buf[0] == '@') 
                            break;
                        for(j = 0; j < len; j++) {
                                buf[j] = toupper(buf[j]);
                        }
                        write(newfd, buf, len);
                        memset(buf, 0, 256);
//                        printf("remove client on fd %d\n", newfd);
//                        close(newfd);
//                        FD_CLR(newfd, &master);
                    }
//                    FD_CLR(newfd, &read_fds);
                    printf("remove client on fd %d\n", newfd);
                    close(newfd);
                    FD_CLR(newfd, &master);
                }
            }
        }
    }

//    fail: return 8;
}

  上面的代碼里有我調試的列印信息,這樣就可以更清晰得看到select()函數是如何得阻塞,如何得得知文件描述符的變化。當然,這個函數我也感覺沒太弄清楚,等弄清楚之後在準備詳細得寫一篇關於I/O多路復用的小文章。同時,上面的代碼還存在一個問題,那就是服務端只能一個一個得為客戶端服務,也就是所謂的同步。

  以上。

 

參考:《Linux/UNIX系統編程手冊》

   《Linux編程技術詳解》

   《Beej's Guide to Network Programming Using Internet Sockets》

     http://beej.us/guide/bgnet/output/html/multipage/index.html

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1:過濾器概念 過濾器就是一種在請求目標資源的中間組件,比喻把污水轉換成純凈水中間需要一個污水凈化設備,那麼這個設備就好比一個過濾器。那麼我用圖來表示過濾器(可以有多個過濾器)運行的過程 2:Filter(過濾器)介面 Filter介面包含三個方法分別是init、doFilter、 destroy下 ...
  • //從鍵盤錄入學生信息(僅姓名和成績)並輸出。public class StuInformation {//此處命名用StuScore可能更恰當 String name; double score;}import java.util.Scanner; A_zhi 2016.08.18.22.00 ...
  • 電腦的發展歷史 電腦的學名叫電腦,電腦是用來做計算的。在古時候,人們最早使用的計算工具可能是手指,英文單詞“digit”既有“數字”的意思,又有“手指“的意思。古人用石頭打獵,所以還有可能是石頭來輔助計算。 缺點:手指和石頭太低效了 後來出現了”結繩 “記事。 缺點:結繩慢,繩子還有長度限制。 又 ...
  • 這一篇首先從allitebooks.com里抓取書籍列表的書籍信息和每本書對應的ISBN碼。 一、分析需求和網站結構 allitebooks.com這個網站的結構很簡單,分頁+書籍列表+書籍詳情頁。 要想得到書籍的詳細信息和ISBN碼,我們需要遍歷所有的頁碼,進入到書籍列表,然後從書籍列表進入到每本 ...
  • 1. 基本的代碼結構為: 2. ...
  • 1、 set(集合)——包含了經過排序了的數據,這些數據的值(value)必須是唯一的。 也就是說輸入set容器後得到數據,會去重併排序。 s.insert()插入一個元素 s.begin() s.end()分別返迴首尾指針 s.clear() 清空集合 遍歷需要利用迭代器set<類型>::iter ...
  • 程式主文件標誌if __name__=="__main__": 在程式執行python 1.py 時候 程式1.py __name__ 為 main調用其他文件是,__name__ 為程式自己名字等於__name__ 一切事物都是對象,對象是有類創建的 int 內部功能a = 95b = 10c = ...
  • --> 這裡用到兩種方法...其實也不算兩種,就一點點不一樣而已... > Test 測試類 --> MyThread類即線程實現類 --> 邪惡的分割線 --> Test測試 --> MyThread線程實現類 --> 感覺第二種也完全是多餘的啊,就是一種方法... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...