伺服器、客戶端簡單互動程式
這是一個簡單的TCP伺服器/客戶端的程式示例。客戶端傳送兩個long型變數到伺服器端,伺服器端讀取這兩個long型變數並返回這兩個變數的和給客戶端。
這是伺服器端的示例程式碼:
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #define MAXLINE 1024 #define LISTENQ 5 void str_echo(int connfd); void sig_chld(int sign); void err_sys(char *str); int main(void) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in servaddr, cliaddr; if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(4321); //4321埠是自己填寫的臨時埠,只要和客戶端上填寫的伺服器埠是該臨時埠就可以了 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) err_sys("bind error"); if(listen(listenfd, LISTENQ) < 0) err_sys("listen error"); signal(SIGCHLD, sig_chld); for( ; ; ) { clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) { //if(errno == EINTR) //如果在Linux下的vim編寫的話可以把這三行註釋到的程式碼給加上去 // continue; //else err_sys("accept error"); } if((childpid = fork()) == 0) { printf("I am in child %d\n", getpid()); close(listenfd); str_echo(connfd); close(connfd); exit(0); } close(connfd); } return 0; } /* * the function to handle INT of SIGCHLD */ void sig_chld(int sign) { pid_t pid; int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); return; } /* * send the sum of two long numbers to client */ void str_echo(int sockfd) { long arg1, arg2; ssize_t n; char line[MAXLINE]; for( ; ; ) { if((n = read(sockfd, line, MAXLINE)) == 0) return; if(sscanf(line, "%ld%ld", &arg1, &arg2) == 2) snprintf(line, sizeof(line), "%ld\n", arg1 + arg2); else snprintf(line, sizeof(line), "input error"); n = strlen(line); if(write(sockfd, line, n) != n) err_sys("write error"); } } void err_sys(char *str) { perror(str); exit(1); }
以下是客戶端的示例程式碼
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #define MAXLINE 1024 void str_cli(int sockfd); void err_sys(char *str); int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in servaddr; if(argc != 2) { printf("usage: ./a.out <ip>\n"); exit(1); } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); printf("sockfd is ok\n"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(4321); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0) err_sys("inet_pton error"); if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) err_sys("connect error"); printf("connect is ok\n"); str_cli(sockfd); close(sockfd); return 0; } /* * send two long num to server host */ void str_cli(int sockfd) { char sendline[MAXLINE], recvline[MAXLINE]; while(fgets(sendline, MAXLINE, stdin) != NULL) { if(write(sockfd, sendline, strlen(sendline)) < 0) err_sys("write error"); if(read(sockfd, recvline, MAXLINE) == 0) err_sys("read error"); fputs(recvline, stdout); bzero(recvline, strlen(recvline)); } } void err_sys(char *str) { perror(str); exit(1); }
基本套接字函式講解
socket函式
#include <sys/socket.h>
int socket(int family, int type, int protocol); //成功返回非負描述符,出錯返回-1
socket函式指定期望的通訊協議型別(使用IPv4的TCP、使用IPv6的UDP、Unix域位元組流協議)和套接字字型別(位元組流、資料報或原始套接字)
----socket函式的family常值 ---------------
family 說明
AF_INET IPv4協議
AF_INET6 IPv4協議
AF_LOCAL Unix協議域
AF_ROUTE 路由套接字
AF_KEY 祕鑰套接字
----------------------------------------------------
----socket函式的type常值 ------------------
SOCK_STREAM 位元組流套接字
SOCK_DGRAM 資料報套接字
SOCK_SEQPACKET 有序分組套接字
SOCK_RAW 原始套接字
----------------------------------------------------
----socket函式的protocal常值 ------------
IPPROTO_CP TCP傳輸協議
IPPROTO_UDP UDP傳輸協議
IPPROTO_SCTP SCTP傳輸協議
----------------------------------------------------
connect函式
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); //成功返回0,出錯為-1
TCP客戶用connect函式來建立一個與TCP伺服器連線,sockfd是由socket函式返回的套接字描述符,第二個、第三個引數分別是指向一個套接字地址結構的指標和該結構的大小,套接字結構必須含有伺服器的IP地址和埠號。如果connect失敗後,就必須close當前的套接字描述符並重新呼叫socket。bind函式
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); //成功返回0,出錯-1
bind函式把一個本地協議地址賦予一個套接字,它只是把一個協議地址賦予一個套接字,至於協議地址的含義則取決於協議本身。第二個引數指向協議地址結構的指標,第三個引數是協議地址的長度,對於TCP,呼叫bind函式可以指定一個埠號,或指定一個IP地址,或兩者都指定,也可以兩者都不指定。
listen函式
#include <sys/socket.h>
int listen(int sockfd, int backlog); //成功返回0,出錯-1
socket建立一個套接字時,它被假設為一個主動套接字,也就是說,它是一個將呼叫connect發起連線的一個客戶套接字。listen函式把一個未連線的套接字轉換為一個被動套接字,指示核心應接受指向該套接字的連線請求,呼叫listen函式將導致套接字從CLOSEE狀態轉換到LISTEN狀態。第二個引數規定了核心應為相應套接字排隊的最大連線個數。(1)、未完成連線佇列:每一個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三路握手過程。這些套接字處於SYN_RCVD狀態。
(2)、已完成連線佇列:每個完成TCP三路握手過程的客戶對應其中一項,這些套接字處於ESTABLISHED狀態。
------------------------------------------------------------------------------------------------------------------------------------------------------
TCP監聽套接字維護的兩個佇列
accept函式
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //成功返回非負描述符,出錯返回-1
如果accept成功,那麼其返回值是由核心自動生成的一個全新套接字,代表與返回客戶的TCP連線,函式的第一個引數為監聽套接字,返回值為已連線套接字。------------------------------------------------------------------------------------------------------------------------------------------------------
伺服器、客戶端程式流程圖
------------------------------------------------------------------------------------------------------------------------------------------------------
TCP狀態轉換圖
------------------------------------------------------------------------------------------------------------------------------------------------------
參考資料:
1、《UNIX網路程式設計 卷1 套接字聯網API》 第四章-基本套接字程式設計
2、《計算機網路 - 謝希仁》