Linux Socket Programming 淺談 |
|
conundrum
尊榮會員 發表:893 回覆:1272 積分:643 註冊:2004-01-06 發送簡訊給我 |
編程文章: Linux Socket Programming 淺談 -- 教你的程式如何透過網路溝通 此文章由 zunix 發表於11月30日 (星期日) 下午12:08 http://www.linuxhall.org/modules.php?name=News&file=article&sid=79 眾所周知,Networking 一向也是 Unix/Linux 的強項,你可以架設各種不同的 Linux 伺服器,但是,你又否想過你也可以在 Linux 上寫自己的伺服器程式呢?這次我便為大家淺談如何寫一個簡單的 Server-client program,讓大家一嘗自己寫伺服器程式的滋味。 作者: Kam Tik Email: kamtik@www.linux.org.hk 當然,你必需先學會寫 C 才可以寫 server-client program。在這裡,我會假設大家已經學會。這裡我會分開兩部份,分別是用戶端和伺服端。先看看如何寫用戶端程式吧,為了了解 client program 的基本運作原理,我們首先看看 client program 的流程圖。 由上圖可以看到,程式的流程是先建立了 socket data,接著便是連接伺服器,然後便可以寫進或讀取資料,而這個過程可以重複,直至寫入和讀取完所需資訊後,才關閉連接,過程非常似在硬碟中存取資料。 因此,我們的第一步是要建立 socket data。首先我們要匯入一個程式館 (library): #include #include 這兩個 header file 內含所需的函式和 structure 定義。像使用 open() 函式去建立一個 file descriptor 用來存取硬碟中的資料一樣,我們可以用 socket() 函式去建立一個 socket descriptor,socket() 的一般用法如下: Prototype: int socket (int domain, int type, int protocol); 其中 domain 可以是常數值 PF_INET、PF_LOCAL、PF_IPX 或 PF_INET6: PF_INET 使用 IPv4 協定制式 PF_LOCAL 一般只用作 system logger 或 print queue PF_IPX Novell 網絡協定制式 PF_INET6 使用 IPv6 協定制式 而 type 就選用 SOCK_STREAM 常數值,可以作 byte stream 傳輸。而 protocol 是 network byte order ,SOCK_STREAM 只支援 protocol = 0。如果 socket() 函式發生錯誤,便會回傳負數值。 socket() 的例子: int sockfd; sockfd = socket(PF_INET, SOCK_STREAM, 0); 建立了 socket descriptor 後,我們便可以用 connect() 函式連接到伺服端了: connect() 的 prototype: int connect(int sd, struct sockaddr *server, int addr_len); 其中 sd 是剛才的 socket descriptor,*server 是一個 structure 包含著伺服端的描述, addr_len 是 *server 所指著的 structure 的大小。要建立這個 structure,我們先要宣告這個 structure: struct sockaddr_in dest; 然後再初始化 (initialize) 這個 structure 的某些值,如下: bzero(&dest, sizeof(dest)); dest.sin_family = PF_INET; dest.sin_port = htons(8889); inet_aton("127.0.0.1", &dest.sin_addr); bzero 會把 dest 中的變數的值變為 0,然後把 dest 中的 sin_family 設為 domain (PF_INET, PF_LOCAL, PF_IPX, PF_INET6),第三句中 8889 是 port number,是伺服端中要連接到的那一個 port number,而第四句中 "127.0.0.1" 這個字串是伺服器的 IP 位址,在這裡即是機器本身。 connect() 的例子: connect(sockfd, &dest, sizeof(dest)); 如果 connect 過程中有錯誤 (例如未連接網路),便會回傳 0。 到第三階段,可以存取資料了,要讀取資料,我們可以用 recv() 函式: recv() 的 prototype: int recv(int sockfd, void* buf, int maxbuf, int options); 其中 sockfd 是 socket 傳回的 socket descriptor,buf 是收到資料後存放的緩沖位置,maxbuf 是緩沖區 buf 的大小,options 是一些選項 (MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_ERRQUEUE, MSG_NOSIGNAL, MSG_ERRQUEUE),在這裡的示範會使用 0。recv() 會回傳收到資訊的大小值,如有錯誤,會回傳負數值。 recv() 的例子: char buffer[128]; recv(sockfd, buffer, sizeof(buffer), 0); 要傳輸資料,我們可以用 send() 函式: send() 的 prototype: int send(int sockfd, void *buffer, int msg_len, int options); 其中 sockfd、buffer 和 msg_len 和 recv() 的相同,只不過是這次把要傳輸的資訊先放進 buffer 罷了。而 options 有 MSG_OOB, MSG_DONTROUTE, MSG_DONTWAIT, MSG_NOSIGNAL,在這裡我們也會使用 0。和 recv() 一樣,send() 會回傳傳輸的總大小值。 send() 的例子: char buffer[] = "Hello World!"; send(sockfd, buffer, sizeof(buffer), 0); 最後可以關閉連結了!方法非常簡單,只要把 socket descriptor 交給 close() 函式便可。 close() 例子: close(sockfd); 結合以上多項,我們便可以合為一個用戶端程式,範例如下 (sample-client.c): 注: 並沒有 error handling,如果伺服器不是機器本身,請修改 "127.0.0.1" 為伺服器的 IP。例如 "192.168.1.1"。 #include #include #include int main() { int sockfd; struct sockaddr_in dest; char buffer[128]; /* create socket */ sockfd = socket(PF_INET, SOCK_STREAM, 0); /* initialize value in dest */ bzero(&dest, sizeof(dest)); dest.sin_family = PF_INET; dest.sin_port = htons(8889); inet_aton("127.0.0.1", &dest.sin_addr); /* Connecting to server */ connect(sockfd, (struct sockaddr*)&dest, sizeof(dest)); /* Receive message from the server and print to screen */ bzero(buffer, 128); recv(sockfd, buffer, sizeof(buffer), 0); printf("%s", buffer); /* Close connection */ close(sockfd); return 0; } 要編釋這個程式,使用指令: gcc sample-client.c -o sample-client 要執行程式可以使用指令: ./sample-client 不過在伺服器並未架設前請不要執行程式。 好了,這次我們要學寫伺服器程式,並實伺服器和用戶端程式的流程分別不大,只是多了幾個步驟: 中間由 close(client) 到 accept 的箭咀是個循環 (loop),而且這個循環是無限的,代表它不斷接受用戶端的連接。 我們要用 bind() 來設定一個 port number 給這個伺服器程式: bind() 的 prototype: int bind(int sockfd, struct sockaddr* addr, int addrlen); 其中 addr 也是 sockaddr structure,和先前的初始化過程一樣,不過 sin_addr.s_addr 就改設為 INADDR_ANY。而 addrlen 便是 addr 的大小。 接著用 listen() 使程式可接收 socket: listen() 的 prototype: int listen(int sockfd, int queue_len); queue_len 是可被連接數目的最大值。如沒有錯誤,它回傳 0。 要等待及接受連接,我們要使用 accept() 函式: accept 的 prototype: int accept(int sockfd, struct sockaddr *addr, int *addr_len) 它會回傳一個 socket descriptor,用作存取。 結合這些新的元素和新的流程圖,便可以寫伺服器程式了。這個範例是 sample-client 的伺服器程式 (sample-server.c):注: 並沒有 error handling #include #include #include int main() { int sockfd; struct sockaddr_in dest; char buffer[] = "Hello World!"; /* create socket , same as client */ sockfd = socket(PF_INET, SOCK_STREAM, 0); /* initialize structure dest */ bzero(&dest, sizeof(dest)); dest.sin_family = PF_INET; dest.sin_port = htons(8889); /* this line is different from client */ dest.sin_addr.s_addr = INADDR_ANY; /* Assign a port number to socket */ bind(sockfd, (struct sockaddr*)&dest, sizeof(dest)); /* make it listen to socket with max 20 connections */ listen(sockfd, 20); /* infinity loop -- accepting connection from client forever */ while(1) { int clientfd; struct sockaddr_in client_addr; int addrlen = sizeof(client_addr); /* Wait and Accept connection */ clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen); /* Send message */ send(clientfd, buffer, sizeof(buffer), 0); /* close(client) */ close(clientfd); } /* close(server) , but never get here because of the loop */ close(sockfd); return 0; } 編釋: gcc sample-server.c -o sample-server 執行: ./sample-server 執行後它便開始接受連接,可以執行 sample-client 試試,便可以接收到 "Hello World!" 這個訊息了!要結束 sample-server 可以按 Ctrl-C。要把 sample-server 放在 background 中工作,可以用 ./sample-server & 如各位想下載源程式碼回來試試,可以到: http://www.linux.org.hk/~kamtik/linuxhall/sample-client.c http://www.linux.org.hk/~kamtik/linuxhall/sample-server.c 原文刊載在 LinuxHall 第 16 期台灣災難都是事後算帳 無人飛行載具(Unmanned Aerial Vehicle,UAV)為什麼沒大量應用於救災行列 絲絲有2種 .net有很多種 一種治眼睛是MS 另一種治腦筋是Borland |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |