Socket程式設計(二)回射客戶/伺服器的實現
TCP客戶/伺服器模型,即C/S模型
可以將這個模型類比為打電話。
TCP伺服器通過socket()建立一個套接字,相當於安裝一臺話機;然後為安裝好的話機繫結bind()一個電話號碼;讓話機處於監聽的狀態listen();等待客戶端的連線accept(),也就是等待對方電話撥打過來,如果一直沒有電話撥打過來,那就一直阻塞,知道客戶端連線到達;
對於客戶端來說,它也需要安裝一部話機socket();接下來呼叫connect()函式,撥打電話號碼;撥通之後就建立了連線,一旦連線建立之後雙方就開始進行通訊;
客戶端通過write()函式發起請求;伺服器通過read()函式接收請求,接收到請求後對請求進行處理;然後伺服器通過write()函式將處理的結果向客戶端進行應答;客戶端通過read()函式讀取伺服器發來的應答;這是一個迴圈,如果客戶端還有請求可繼續傳送,伺服器會根據客戶端傳送的請求進行應答;
在通訊過程中,任何一端都可以終止通訊,通過呼叫close()函式來終止通訊;比如客戶端發起一個close,這是一個檔案描述符EOF的通知,伺服器讀到read()通知就會呼叫close()將自己的套接字關閉掉。
這就是一個基本的C/S模型
回射客戶/伺服器模型
客戶端從標準輸入(stdin)選擇一行資料,通過網路傳送(write)給伺服器一端,伺服器讀取(read)資料,不做任何處理,將資料原封不動的傳送(write)給客戶端,客戶端讀取資料(read)並標準輸出(stdout)。
這是一個基本的回射客戶/伺服器模型,下面是實現這個模型所用到的函式:
socket函式
包含標頭檔案:<sys/socket.h>
功能:建立一個套接字用於通訊
函式原型:int socket(int domain,int type,int protocol);
相關引數:domain指定通訊協議族;type指定socket型別,流式套接字SOCK_STREAM、資料包套接字SOCK_DGRAM、原始套接字SOCK_RAW;protocol指定協議型別。
返回值:成功返回非負整數,它與檔案描述符類似,我們把它稱為套介面描述字,簡稱套接字;失敗返回-1 .
bind函式
包含標頭檔案:<sys/socket.h>
功能:繫結一個本地地址到套接字
函式原型:int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen); //將IPv4的地址強制轉化為通用地址長度
相關引數:sockfd是socket函式返回的套接字;addr是要繫結的地址;addrlen是地:址長度。
返回值:成功返回0,失敗返回-1;
listen函式
包含標頭檔案:<sys/socket.h>
功能:將套接字用於監聽進入的連線,將套接字從close狀態轉換成監聽(listen)狀態
函式原型:int listen(int sockfd,int backlog);
相關引數:sockfd是socket函式返回的套接字;backlog規定核心為此套接字排隊的最大連線個數。
返回值:成功返回0,失敗返回-1.
一旦呼叫listen函式後,這個套接字就變成了被動套接字,否則預設是主動套接字。主動套接字是用來發起連線的(通過呼叫connect函式),被動套接字是用來接收連線的(通過呼叫accept函式)。
一般來說,listen函式應該在呼叫socket和bind函式之後,呼叫函式accept之前呼叫。
對於給定的監聽套介面,核心要維護兩個佇列:已由客戶端發出併到達伺服器,伺服器正在等待完成響應的TCP三次握手過程;已完成連線的佇列。
連線需要通過三次握手,三次握手時會發送一些SYN分節過來,這時需要兩個佇列來維護,一是未完成連線佇列,一旦三次握手成功之後,就會將這個分節移動到已完成連線佇列裡邊。listen函式的第二個引數backlog代表未完成連線佇列與已完成連線佇列之和。伺服器呼叫accept函式之後就會從已完成連線佇列取走一個SYN分節,以便更多的客戶端可以發起連線。所以backlog規定了能夠併發連線過來的套介面數目,它的值等於未完成連線數目和已完成連線數目。
accept函式
包含標頭檔案:<sys/socket.h>
功能:從已完成佇列返回第一個連線,如果已完成連線佇列為空,則阻塞。
函式原型:int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);
相關引數:sockfd是伺服器套接字;addr將返回對等方的套接字地址;addrlen返回對等方的套接字地址長度。
返回值:成功返回非負整數,表示建立了一個新的套接字;失敗返回-1。
connect函式
包含標頭檔案:<sys/socket.h>
功能:建立一個連線至addr所指定的套接字
函式原型:int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
相關引數:sockfd是未連線的套接字;addr是要連線的套接字地址;addrlen是第二個引數addr的長度。
返回值:成功返回0,失敗返回-1.
以下是回射客戶/伺服器程式碼
伺服器端echo-server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
int main(void)
{
//create a socket
int sockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0) //前兩個引數已經決定是TCP協議
//第三個引數可以直接寫0,如下
/*if((sockfd=socket(AF_INET,SOCK_SCREAM,0))<0); //SOCK_SCREAM TCP 0 */
ERR_EXIT("socket error!");
//initialize address
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188); //將主機位元組序轉化為網路位元組序,s代表兩個位元組。
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //0.0.0.0 all of the host address
//這裡的htonl可以省略掉,因為全0,轉化也為0
/*servaddr.sin_addr.s.addr=inet_addr("127.0.0.1"); //將點分式十進位制地址轉化為32位地址*/
/*inet_aton("127.0.0.1",&servaddr.sin_addr); //將點分式十進位制地址轉化位網路地址*/
//bind將套接字與本地地址進行繫結
if(bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind error!");
//listen將套接字用於監聽進入的連線
if(listen(sockfd,SOMAXCONN)<0) //SOMAXCONN這個巨集代表隊列最大值
ERR_EXIT("listen error!");
//accept從已完成連線佇列的隊頭得到一個連線
struct sockaddr_in peeraddr; //define peer address
socklen_t peerlen=sizeof(peeraddr); //對方的地址長度
int conn; //define a new socket,the accept will return it.
if((conn=accept(sockfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept error!");
//通訊
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret=read(conn,recvbuf,sizeof(recvbuf));//讀取新的套接字,這個套接字由accept返回
fputs(recvbuf,stdout);
write(conn,recvbuf,ret);
}
close(conn);
close(sockfd);
return 0;
}
客戶端echo-client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)
int main(void)
{
//create a socket
int sock;
if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
/*if((sock=socket(AF_INET,SOCK_SCREAM,0))<0); //SOCK_SCREAM TCP 0 */
ERR_EXIT("socket error!");
//initialize address
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//連線伺服器端的地址
//不需要繫結、監聽,直接連線即可
//connect發起連線
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect error!");
//通訊
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) //從標準輸入接收一行資料
{
write(sock,sendbuf,strlen(sendbuf));
read(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout); //回射資料
//clear the buf
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}