一個基本的C/S伺服器模型很簡單: 用戶端 <------------------------> 伺服器
簡而言之就是用戶端跟伺服器之間的通話,通話方式一般採用TCP和UDP這兩種。
TCP和UDP區別
1、Tcp提供客戶與伺服器之間的串連。TCP用戶端先與某個給定伺服器建立一個串連,再跨該串連於那個伺服器交換資料,然後終止這個串連。
(串連其實就是一種協商機制,預先定義好了雙方的一些狀態變數,告訴對方諸如序號和通告視窗大小等狀態資訊)
2、Tcp提供了可靠的傳輸機制,不需要像UDP那樣需要通過應用程式層來實現可靠性,直接通過協議來實現。發送資料後等待對方確認,沒有收到確認就繼續重傳與等待,數次重傳失敗後才會放棄。
3、Tcp提供了流量控制,tcp總是告訴對端在任何時刻它一次能夠從對端接收多少位元組的資料。
採用通訊端的TCP串連的基本程式模型
就是通過socket的這個函數封裝資料,實現server額client的通訊
用戶端 服務端
sokcket socket() //建立通訊端描述符
bind() //將伺服器位址和相應通訊端描述符綁定
listen() //將主動通訊端轉換為監聽通訊端,該通訊端可以接受來自用戶端的請求
connect() <-------------> accept() //connect()用戶端串連請求 , accpet()函數解析監聽來的用戶端資訊,
write() ------------> read() //網路讀寫io
read() <-------------- write()
close() ------------> read()
close() //close關閉串連
函數介面
1、socket
#include<sys/types.h>#include<sys/socket.h>int socket(int domain, int type, int protocol);若成功返回非負描述符,若出錯返回-1
domain網路類型(一般預設為AF_INET網際網路),type通訊端類型(一般預設為SOCK_STREAM),protocol協議號
2、connect
#include<sys/socket.h>int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);若成功返回0,若出錯返回-1
connect函數試圖與通訊端地址為serv_addr的伺服器建立一個網際網路串連,addrlen預設為sizeof(sockaddr_in)
3、bind
#include<sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, int addrlen)若成功返回0,若出錯返回-1
將伺服器位址和通訊端聯絡起來
4、listen
#include<sys/socket.h>int listen(int sockfd, int backlog);若成功返回0,若出錯返回-1
告訴核心,這個是伺服器建立的通訊端,不是用戶端的,將主動狀態轉換為被動狀態。
5、accept
#include<sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
若成功返回0,若出錯返回-1
等待來自於用戶端的串連請求到達監聽描述符listenfd,然後在addr中填寫用戶端的通訊端地址,並返回一個已串連描述符,這個描述符可以用來利用Unix I/O函數與用戶端通訊。
通訊端地址結構體
<netinet/in.h>struct in_addr{ in_addr_t s_addr; //32bit ipv4 address}struct sockaddr_in{ uint8_t; sa_family_t sin_family; // AF_INET in_port_t sin_port; //連接埠 struct in_addr sin_addr; char sin_zero[8]; //未使用};
DNS主機條目結構體
通過調用gethostbyname和gethostbyaddr函數,從DNS資料庫中檢索任意的主機條目。
<pre name="code" class="cpp">#include <netdb.h>
struct hostent{ char *h_name; // 主機網域名稱 char **h_aliases; int h_addrtype; //主機地址 int h_length; char **h_addr_list;
};
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const char* addr, int len, int domain);
具體代碼實現:
實現一個簡單的用戶端伺服器模型,並不涉及多線程、I/O複用此類
server.c 服務端
#include<stdio.h> #include<sys/socket.h> #include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<netdb.h>#include<arpa/inet.h>#define MAX_LISTEN 1024#define MAX_LINE 1024int Socket(int domain, int type, int protocol){ int sockfd = socket(domain, type, protocol); if ( sockfd < 0 ){ perror("init socket: "); exit(0); } return sockfd;}void Bind(int sockfd, struct sockaddr *myaddr, int addrlen){ if ( bind(sockfd, myaddr, addrlen) < 0 ){ perror("bind"); exit(0); } }void Listen(int sockfd, int backlog){ if ( listen(sockfd, backlog) < 0){ perror("listen"); exit(0); }}int Accept(int listenfd, struct sockaddr *addr, int *addrlen){ int clientfd = accept(listenfd, addr, addrlen); if ( clientfd < 0){ perror("accept"); exit(0); } return clientfd;}void Close(int clientfd){ if ( close(clientfd) < 0){ perror("close"); exit(0); }}struct hostent* Gethostbyaddr(const char *addr, int len, int domain){ struct hostent* host = gethostbyaddr(addr, len, domain); if ( NULL == host ){ perror("host_by_addr"); exit(0); } return host;}ssize_t Read(int fd, void* buf, size_t n){ ssize_t num= read(fd, buf, n); if ( n < 0){ perror("read"); exit(0); } return num;}ssize_t Write(int fd, const void* buf, size_t n){ ssize_t num = read(fd, buf, n); if ( n < 0){ perror("write"); exit(0); } return num;}void echo(listenfd){ ssize_t n; char write_buff[MAX_LINE]; char read_buff[MAX_LINE]; memset(write_buff, 0, MAX_LINE); memset(read_buff, 0, MAX_LINE); n = read(listenfd, read_buff, MAX_LINE); read_buff[n] = '\0'; strcpy(write_buff, "from server echo: "); strcpy(write_buff+strlen("from server echo: "), read_buff); n = write(listenfd, write_buff, MAX_LINE); }int main(int argc, char **argv){ int servfd, clientfd, port, clientlen; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; struct hostent *host; char* hostaddr; if ( argc != 2){ fprintf(stderr,"usage:%s<port>\n", argv[0]); exit(0); } port = atoi(argv[1]); // get port servfd = Socket(AF_INET, SOCK_STREAM, 0); // init servaddr memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons((unsigned short)port); clientlen = sizeof(cliaddr); Bind(servfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); Listen(servfd, MAX_LISTEN); while(1){ // init server memset(&cliaddr, 0, sizeof(cliaddr)); clientfd = Accept(servfd, (struct sockaddr*)&cliaddr, &clientlen); host = Gethostbyaddr((const char*)&cliaddr.sin_addr.s_addr, sizeof(cliaddr.sin_addr.s_addr), AF_INET); printf("server connect to host: %s %s\n",host->h_name, inet_ntoa(cliaddr.sin_addr)); echo(clientfd); Close(clientfd); }}
client.c 用戶端
#include<stdio.h> #include<sys/socket.h> #include<sys/types.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<netdb.h>#define MAX_LINE 1024int Socket(int domain, int type, int protocol){ int sockfd = socket(domain, type, protocol); if ( sockfd < 0 ){ perror("init socket"); exit(0); } return sockfd;}void Close(int clientfd){ if ( close(clientfd) < 0){ perror("close"); exit(0); }}struct hostent* Gethostbyaddr(const char *addr, int len, int domain){ struct hostent* host = gethostbyaddr(addr, len, domain); if ( NULL == host ){ perror("host_by_addr"); exit(0); } return host;}ssize_t Read(int fd, void* buf, size_t n){ if ( read(fd, buf, n) < 0){ perror("read"); exit(0); }}ssize_t Write(int fd, const void* buf, size_t n){ if ( write(fd, buf, n) < 0){ perror("write"); exit(0); }}void Connect(int sockfd, struct sockaddr* serv_addr, int addrlen){ if ( connect(sockfd, serv_addr, addrlen) < 0){ perror("connect"); exit(0); }}void message_handle(int clientfd){ size_t n; char send_buff[MAX_LINE]; char recv_buff[MAX_LINE]; memset(send_buff, 0, MAX_LINE); memset(recv_buff, 0, MAX_LINE); fgets(send_buff, MAX_LINE, stdin); send_buff[strlen(send_buff)-1] = '\0'; n = Write(clientfd, send_buff, strlen(send_buff)+1); n = Read(clientfd, recv_buff, MAX_LINE); printf("%s \n", recv_buff);}int main(int argc, char **argv){ int clientfd, port; struct sockaddr_in servaddr; if ( argc != 3){ fprintf(stderr,"usage:%s<addr> <port>\n", argv[0]); exit(0); } port = atoi(argv[2]); printf("port: %d\n", port); printf("addr: %s\n", argv[1]); clientfd = Socket(AF_INET, SOCK_STREAM, 0); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(clientfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); message_handle(clientfd); Close(clientfd);}
程式碼分析:
服務端按上圖TCP模型建立伺服器處理用戶端串連,每當有用戶端串連時即列印客戶資訊,然後接受到客戶發來的訊息後,將客戶訊息反射回用戶端。
運行結果:
用戶端:
服務端:
用戶端中斷後使用netstat -a 命令查看tcp串連狀態,9000連接埠處於listen狀態,該串連處於TIME_WAIT狀態