在本篇文章中,先介紹一下Socket編程的一些API,而後利用這些API實現一個客戶端-服務器模型的一個簡單通訊例程。該例子中,服務器接收到客戶端的信息後,將信息從新發送給客戶端。編程
socket()函數用於建立一個套接字。這就好像購買了一個電話。不過該電話尚未分配號碼。服務器
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol)
參數說明:網絡
domain:指定通訊的協議族,這些協議族定義在頭文件< sys/socket.h >中。使用IPV4協議族時,該參數設置爲AF_INET。dom
type :指定socket的類型。在上一篇文章中介紹過,套接字經常使用的有三種類型:流式套接字SOCK_STREAM,數據報套接字SOCK_DGRAM,原始套接字SOCK_RAW。socket
protocol : 該參數指定了一種協議類型用於所選擇的套接字。若是僅有一種協議支持某種套接字類型,那麼該參數能夠定義爲0,此時使用默認協議;若是一種套接字類型可能有多種協議類型,那麼必須顯式指定協議類型。關於具體細節,能夠man socket進行查閱。函數
socket()的返回值:成功時返回非負整數;失敗時返回-1;ui
bind()函數綁定一個本地地址到套接字上,這至關於爲電話綁定了號碼。當一個套接字經過socket()被建立,它並無綁定到具體的地址上,bind()來完成這個步驟。 bind()函數的函數原型以下:code
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數說明:接口
sockfd:socket()函數建立後成功返回的套接字隊列
addr : 須要綁定的地址
addrlen:套接字的大小
這裏須要使用到sockaddr_in結構來表示一個地址,該結構以下:
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; }; struct in_addr { uint32_t s_addr; }
sockaddr_in須要強制轉換爲struct sockaddr*類型,傳遞給bind()函數的第二個參數。下面是一段例程:
int main() { int listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd == -1) err_exit("socket error"); struct sockaddr_in addr; //填充結構 addr.sin_family = AF_INET; addr.sin_port= htons(8001); //主機字節序轉換爲網絡字節序 addr.sin_addr= htonl(INADDR_ANY);//綁定主機的任一個IP地址 /*下面兩句具備相同的功能:都是綁定到本機ip地址*/ //inet_aton("127.0.0.1",&addr.sin_addr); //addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(bind(listenfd,(const struct sockaddr*)&addr,sizeof(addr))==-1) err_exit("bind error"); }
當使用socket()建立了一個套接字時,該套接字默認是主動套接字。使用listen()函數會使套接字稱爲一個被動套接字,也就是說,該套接字將被用來接受鏈接的數據,這些數據經過accept()函數接收。
listen()函數的函數原型以下:
#include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);
參數說明:
sockfd : 套接字。
backlog: 指定鏈接隊列的長度。
對於給定的監聽套接字,內核須要維護兩個隊列:
已完成鏈接隊列:該隊列中的鏈接處於ESTABLISHED狀態,也便是已經完成了三次握手過程。
未完成鏈接隊列:該隊列中的鏈接處於SYN_RCVD狀態,還未創建鏈接。
兩個隊列的長度之和不可以超過backlogi。若是一個鏈接請求到達時未完成隊列已滿,客戶端可能接收到一個錯誤指示ECONNREFUSED。服務器使用accept()函數從已完成鏈接隊列的隊頭返回一個鏈接。下面是TCP爲監聽套接口維護的兩個隊列:
accept()函數用於從已完成隊列的隊頭返回一個鏈接。它的函數原型爲:
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數說明:
sockfd : 服務器套接字
addr :用於接收對等方(客戶端)的套接字地址。該參數填充爲NULL時,不接收任何信息。
addrlen:返回對等方的套接字地址長度。若是不關心能夠設置爲NULL,不然必定要初始化。
函數返回值:成功返回一個非負整數,表明一個套接字;失敗返回-1;
該函數用於創建一個鏈接到指定的套接字。函數的原型爲:
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數說明:
sockfd : 未鏈接的套接字
addr:未鏈接的套接字地址
addrlen:addr的長度
客戶端代碼:
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { /*建立一個套接字*/ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock == -1) ERR_EXIT("socket"); /*定義一個地址結構*/ struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5888); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /*進行鏈接*/ if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("connect"); } else { printf("鏈接成功\n"); } char sendbuf[1024]={0}; char recvbuf[1024]={0}; /*從標準輸入中讀入*/ while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(sock ,sendbuf,strlen(sendbuf)); if(read (sock,recvbuf,sizeof(recvbuf))>0) { printf("從服務器接收信息:\n"); fputs(recvbuf,stdout); } memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); } close(sock); return 0; }
服務器端代碼:
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<arpa/inet.h> #include<netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main() { /* 建立一個套接字*/ int listenfd= socket(AF_INET ,SOCK_STREAM,0); if(listenfd==-1) ERR_EXIT("socket"); /*定義一個地址結構並填充*/ struct sockaddr_in addr; addr.sin_family = AF_INET; //協議族爲ipv4 addr.sin_port = htons(5888); //綁定端口號 addr.sin_addr.s_addr = htonl(INADDR_ANY);//主機字節序轉爲網絡字節序 /*將套接字綁定到地址上*/ if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1) { ERR_EXIT("bind"); } /*監聽套接字,成爲被動套接字*/ if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("Listen"); } struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); /*定義一個套接字,一般稱爲已鏈接套接字*/ int conn ; conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn <0) ERR_EXIT("accept error"); else printf("鏈接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port)); /*循環獲取數據、發送數據*/ char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn,recvbuf ,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,sizeof(recvbuf)); } /*關閉套接字*/ close(listenfd); close(conn); return 0; }