預備知識
套接字
套接字(socket)這個詞可以表示很多概念:在TCP/IP協議中,”IP+地址+TCP/UDP端口號”唯一標示網絡通訊中的一個進程,IP地址+端口號”就稱爲socket。
網絡字節序
網絡中要實現通信,少不了數據的傳輸。所以這裏就引入了網絡字節序的概念。
在前邊的學習中,我們接觸到大端和小端的概念。小端:數據的地位在低地址,高位在高地址;大端:數據的低位在高地址,高位在低地址。網絡數據流同樣也有大端和小端之分。網絡數據流先發出的是低地址,後發出的是高地址。TCP/IP規定,網絡數據流採用大端字節序,即就是低位在高地址。我們之所以會說到大端和小端?是因爲,網絡通信的時候必須知道端口號,如果發送端是大端字節序,接收端是小端字節序,那麼最後看到的端口號就是不正確的端口號,所以,我們必須將端口號在發送端和接收端之間轉換成統一的字節序形式。
爲使網絡程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯後都能正常運行,可以調用以下庫函數做網絡字節序和主機字節序的轉換。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主機字節序轉爲網絡字節序,long類型
uint16_t htons(uint16_t hostshort); //主機字節序爲網絡字節序,short類型
uint32_t ntohl(uint32_t netlong); //網絡節序轉爲主機字節序,long類型
uint16_t ntohs(uint16_t netshort); //主機節序轉爲網絡字節序,short類型
socket編程的操作函數
1.套接字創建函數sockt:
函數功能:創建套接字。
參數:
(1)domain:表示建立的socket的類型,可選擇的類型如下:
對於IPv4,參數指定爲AF_NET
(2)type:表示的是傳輸的數據是 數據報傳輸還是字節流傳輸,可選的類型如下:
對於TCP協議,type參數指定爲SOCK_STREAM,表示面向流的傳輸協議。
如果是UDP協議,type參數指定爲SOCK_DGRAM表示面向數據報的協議。
(3)protocol:表示創建的方式,我們在這裏把他置爲0。
返回值:
成功返回一個文件描述符,失敗返回-1。
2.服務器綁定函數bind
函數功能:服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號後就可以向服務器發起連接,因此服務器需要調用bind綁定一個固定的網絡地址和端口號。
參數:
(1)sockfd:服務器的套接字,也就是socket函數的返回結果。
(2)addr:是socket服務器的地址內容,結構體內的變量則去填寫ip地址和端口號。而sockaddr_in這個結構體成員如下:
這裏有設計到了字符串與in_addr的轉換:
(3)addrlen:指定結構體的長度。
返回值:成功返回0,失敗返回-1。
3.設置監聽狀態函數listen
函數功能:用來設置sockfd套接字爲監聽狀態的,用來監聽客戶端的連接。
參數:
backlog:表示的是服務器連接達到最大的數量之後,還可以放到等待隊列的連接個數,所以一般不要設太大。
返回值:成功返回0,失敗返回-1。
4.請求連接函數connect
函數功能:用於客戶端,用來請求對服務器的連接。
參數:
sockfd表示的表示的是要鏈接到服務器的客戶端套接字。
參數addr表示的是服務器的地址與端口號;
參數addrlen表示的是addr的大小一般使用sizeof得到。
5.接受連接函數accept
函數功能:三次握手完成後,服務器調用accept()接受連接,如果是服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。
參數:
sockfd:已經創建好的文件描述符,也是監聽套接字。
addr:輸出型參數,獲取客戶端的信息(IP地址,端口號等)。
addrlen:是一個傳入傳出參數,傳入的是調用這提供的緩衝區addr的長度以避免緩衝區溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有佔滿調用者提供的緩衝區)。
返回值:成功返回一個文件描述符,是真正的用於通信的桃姐子。失敗返回-1。
單進程的套接字TCP通信
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* name)
{
printf("Usage:%s [IpAddress] [port]\n",name);
}
int StartUp(int port, const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror("listen");
return 3;
}
return ListenSock;
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
socket_t len = sizeof(client);
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);//獲取客戶機的信息
if(sock < 0)
{
perror("accept");
continue;
}
printf("Get a client,IP is %s,port is %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
char buf[1024];
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1);//從服務器讀數據
if(s == 0)
{
//數據已經讀完了,客戶端不發送數據了
printf("client is quit!\n");
return 2;
}
else if(s <0)
{
perror("read");
return 3;
}
else
{
buf[s] = 0;
printf("client# %s\n",buf);
// write(sock, buf, strlen(buf));
printf("say: ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s <= 0)
{
perror("read");
return 1;
}
buf[_s-1]=0;
write(sock,buf,sizeof(buf)-1);
}
}
close(sock);
}
return 0;
}
client.c:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* name)
{
printf("Usage:%s [IpAddress] [port]\n",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socke");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
{
perror("connect");
return 3;
}
//printf("connect success!\n");
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s <= 0)
{
perror("read");
return 4;
}
buf[s-1] = 0;
write(sock,buf,strlen(buf));
ssize_t _s = read(sock, buf, sizeof(buf)-1);
if(_s == 0)
{
printf("server quit\n");
break;
}
else if(_s < 0)
{
perror("read");
return 5;
}
else
{
buf[s]=0;
printf("server #: %s\n", buf);
}
}
close(sock);
return 0;
}
多進程套接字TCP通信
上邊的代碼,只可以一個客戶端進行發送數據,而在實際的應用中,,都是會出現多個客戶端給服務器發送數據,所以,上邊的實現並不實用。所以,我們可以實現一個多進程的socket通信,以實現多個客戶端給服務器發數據。
實現方法:服務器端可以創建多個子進程去處理客戶端發來的信息。當每次收到一個新的客戶端的連接請求的時候,我們就會fork()出一個子進程,父進程用於等待子進程,子進程用於執行 讀客戶端發的數據 的操作。細心的你可能會發現,我們在子進程讀取信息之前還進行了一次fork(),這是爲什麼呢?其實,我們用子進程fork()出一個孫子進程,終止掉兒子進程,兒子進程被它的父進程回收,此時的孫子進程就是一個孤兒進程,被1號進程領養。這樣做的目的就是,不要讓兒子進程等待孫子進程太久而消耗太多的系統資源。
代碼實現:
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]\n",name);
}
int StartUp(int port, const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror("listen");
return 3;
}
return ListenSock;
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
socklen_t len = sizeof(client);
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);
if(sock < 0)
{
perror("accept");
continue;
}
printf("get a client is %s,port is %d\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
int id = fork();
if(id > 0)
{
close(sock);
while(waitpid(-1,NULL,WNOHANG) > 0);
continue;
}
else
{
close(listenSock);
if(fork() > 0)
{
exit(0);
}
char buf[1024];
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1); //服務器進行讀數據
if(s > 0)
{
buf[s] = 0;
printf("client# %s\n",buf);
}
else
{
//數據已經讀完了,客戶端不發送數據了
printf("client is quit!\n");
break;
}
}
close(sock);
break;
}
}
return 0;
}
client.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
{
perror("connec");
return 3;
}
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s < 0)
{
perror("read");
break;
}
buf[s-1] = 0;
write(sock,buf,s);
}
close(sock);
return 0;
}
多線程的套接字通信
實現方法:
主線程中創建出一個新線程,新線程的執行函數是讀取信息。類似於上邊的多進程間的通信,我們可以將新的線程進行分離,分離之後的線程就不需要主線程去等待,而是由操作系統區回收。(這裏我們不可以join新線程,如果這樣做的話,主線程還是需要花費很長的時間去等待,所以,新的線程還是由系統去回收) 。
代碼實現:
server.c
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<pthread.h>
void Usage(const char* name)
{
printf("Usage is %s [IP] [port]\n",name);
}
int StartUp(int port,const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror(("listen"));
return 3;
}
return ListenSock;
}
void* thread_hander(void* arg)
{
int sock = *((int*)arg);
char buf[1024];
while(1)
{
ssize_t _s = read(sock,buf,sizeof(buf)-1);
if(_s > 0)
{
buf[_s-1]=0;
printf("client say# %s\n",buf);
if(write(sock,buf,sizeof(buf)-1) < 0)
{
break;
}
}
else if(_s == 0)
{
printf("client is quit!\n");
break;
}
else
{
perror("read");
break;
}
}
close(sock);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
int len = 0;
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);
if(sock < 0)
{
perror("accept");
continue;
}
printf("Get a client! IP is %d\n,port is %d\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread_hander,&sock);
if(ret < 0)
{
perror("pthread_create");
return 3;
}
pthread_detach(tid);
}
return 0;
}
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
void Usage(const char* name)
{
printf("Usage is %s [IP] [port]\n",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));
if(ret < 0)
{
perror("connect");
return 3;
}
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s > 0)
{
buf[_s -1 ] = 0;
if(write(sock,buf,sizeof(buf)-1) < 0)
{
break;
}
ssize_t s = read(sock,buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("server echo# %s\n",buf);
}
}
else
{
perror("read");
return 4;
}
}
return 0;
}