UNIX 网络编程
目錄
1 socket網絡編程步驟
1.1 服務器編程步驟
1.2 客戶端編程步驟
1.3 函數以及結構解釋
2 基于TCP的一對多網絡編程
2.1 服務器編程步驟
2.2 客戶端編程
2.3 函數解釋
2.4 基于TCP的網絡聊天室
3 基于UDP的一對多編程
3.1 服務器編程步驟
3.2 客戶端編程步驟
3.3 UDP下的讀寫數據
3.4 函數解釋
3.5 基于UDP的時間服務器代碼
本文要講解Unix下的網絡編程,即socket編程,也稱為套接字編程。
Unix系統在網絡上功能十分強大,歷史悠久,因此有一個非常固定的套路,代碼比較僵化,沒有改變的余地。Socket編程主要包括兩個方面,一個是本地通信,即計算機內部的進程間的通信(IPC),這個本文不過多講解,主要集中在后面的內容,網絡通信。
本文先介紹Unix網絡編程的基本框架,以及具體的函數用法、結構體,然后分別介紹TCP網絡編程和UDP網絡編程。本文只介紹編程框架,而不會涉及具體的協議。
1 socket網絡編程步驟
網絡編程要考慮兩個方面:服務器端和客戶端,客戶端通過網絡與服務器端通信。
1.1 服務器編程步驟
1、調用函數socket(),用來創建socket描述符。
int fd=socket(AF_INET,SOCK_DGRAM,0);if(fd==-1)perror("socket"),exit(-1);?
2、準備通訊地址(三個結構體),進行數據交互。
struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(2222);//服務器的端口addr.sin_addr.s_addr=inet_addr("192.168.1.112");//服務器的ip地址?
3、綁定通訊地址和socket描述符,函數bind()
int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1)perror("bind "),exit(-1);elseprintf("綁定成功\n");?
4、讀寫描述符,和讀寫文件描述符一樣,函數read()/write()。
char buf[100]={};res=read(fd,buf,sizeof(buf));?
5、使用函數close()關閉socket描述符。
close(fd);1.2 客戶端編程步驟
1、調用函數socket(),用來創建socket描述符。
int fd=socket(AF_INET,SOCK_DGRAM,0);if(fd==-1)perror("socket "),exit(-1);?
2、準備通訊地址(三個結構體),進行數據交互。
struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(2222);//服務器addr.sin_addr.s_addr=inet_addr("192.168.1.112");?
3、綁定通訊地址和socket描述符,函數connect()
int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1)perror("connect "),exit(-1);?
4、讀寫描述符,和讀寫文件描述符一樣,函數read()/write()。
write(fd,"hello",5);5、使用函數close()關閉socket描述符。
??? close(fd);
除了1.3的bind()換成connect()之外,其他的與服務器端一樣,而且connect()和bind()用法完全一樣。
1.3 函數以及結構解釋
1、socket() 函數
int socket( int domin , int type , int protocol);參數解釋
domin用于選擇協議簇,可以選擇一下的宏:
AF_UNIX/AF_LOCAL/AF_FILE :本地通信
AF_INET : 網絡通信IPv4 (一般使用這個)
AF_INET6 :網絡通信IPv6
type 用于選擇通信的類型,主要包括:
SOCK_STREAM :數據流,用于TCP。
SOCK_DGRAM :數據報,用于UDP。
protocol參數本來應該指定協議,但是由于協議已經被前兩個參數確定了,所以直接將protocol給0即可,這是Unix網絡編程的僵化的體現。
如果函數執行成功,返回socket描述符,失敗返回-1.
這個函數用來生成一個socket描述符。
2、結構體
網絡通信需要三個結構體來指定網絡信息,分別是struct sockaddr、struct sockaddr_un、struct sockaddr_in。其中sockaddr主要用于做函數的參數,并不儲存數據,即沒啥實際的用處,socjaddr_un負責存儲本地通信的地址數據,sockaddr_in負責存儲網絡通信的地址數據。
#include <sys/un.h>struct sockaddr_un{int sun_family; //用于指定協議簇,和socket()要保持一致char sun_path[]; //存socket文件名,作為交互的媒介}#include<netinet/in.h>struct sockaddr_in{int sin_family; //用于指定協議簇,和socket()一致short sin_port; //端口號struct in_addr sin_addr; //存儲IP地址的結構}?
注:sockaddr_in / sockaddr_un 在做參數時必須類型轉換為sockaddr,讀寫數據時,一方讀數據,另一方必須寫數據。
?
3、bind()
int bind(int sockfd , struct sockaddr* addr , socklen_t size);參數解釋
sockfd是socket文件描述符,即socket()函數的返回值。
addr是通信地址的指針,需要做類型轉換
size是通信地址的大小,即sizeof(struct)。
這個函數用來綁定自己的IP地址和端口號
4、connect()
int connect(int sockfd , const struct sockaddr *addr , socklen_t addrlen);參數解釋
sockfd是socket文件描述符,即socket()函數的返回值。
addr是通信地址的指針,需要做類型轉換
size是通信地址的大小,即sizeof(struct)。
TCP客戶通過connect函數與服務端進行通信。
2 基于TCP的一對多網絡編程
所謂的一對多編程,即只有一個服務器,有很多個客戶端,現在基本都是一對多編程。TCP是一個基于連接的協議,在網絡交互中,服務器和客戶端要保持連接,不能斷開。如果出現數據錯誤,TCP會重新發送,保證數據的正確和完整,但是資源的消耗比較大。
2.1 服務器編程步驟
1、socket()得到一個socket描述符
2、準備通信地址 struct sockaddr_in
3、綁定bind(),連接端口
4、監聽函數listen(),這個函數用來控制同一時刻連接的人數。
5、等待客戶端的連接,函數accept(),返回一個新的socket描述符,這個新的socket描述符用于讀寫交互。
6、讀寫函數read()/write()
7、關閉socket()
2.2 客戶端編程
1、調用函數socket(),用來創建socket描述符。
2、準備通訊地址(三個結構體),進行數據交互。
3、綁定通訊地址和socket描述符,函數connect()
4、讀寫描述符,和讀寫文件描述符一樣,函數read()/write()。
5、使用函數close()關閉socket描述符。
這里的步驟和上面的客戶端編程的步驟是一樣的
2.3 函數解釋
1、listen()
該函數主要用于設置當有多個用戶請求時,放入等待隊列,等待隊列的最大長度就是listen設置的。
int listen( int sockfd , int backlog);參數解釋
sockfd 是你要設置的socket描述符
backlog是隊列的最大長度
2、accept()
int accept(int fd , struct sockaddr *addr , socklen_t *len);參數解釋
fd就是socket描述符
addr是一個結構體指針,用于傳出客戶端的通信地址
len是一個傳入傳出參數,傳入addr的真實長度,傳出接收到的客戶端的通信地址的真實長度。(一般是相同的)
返回:成功返回新的socket描述符,失敗返回-1.
2.4 基于TCP的網絡聊天室
1、服務端代碼
#include<stdio.h> #include<stdlib.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<string.h> #include<pthread.h>int sockall[100]; //存放socket描述符的數組 int sum; //聊天室人數int socket_findsit(){for(int i=0;i<100;i++)if(!sockall[i])return i;return -1; }void* task_chat(void* p){int index=(int)p;int fd=sockall[index];char buf[100]={};char name[100]={};char news[200]={};int i=0;sum++;printf("聊天室人數:%d\n",sum);/*讀取用戶的用戶名*/int res=read(fd,name,sizeof(name));printf("用戶%s進入聊天室\n",name);/*進入聊天環節*/while(1){res=read(fd,buf,sizeof(buf));if(res==0)break;if(!strcmp(buf,"bye")){printf("用戶%s退出聊天室\n",name);break;}sprintf(news,"%.*s:%.*s",strlen(name),name,strlen(buf),buf);for(i=0;i<100;i++){if(sockall[i])write(sockall[i],news,strlen(news));}memset(buf,0,strlen(buf));memset(news,0,strlen(news));}close(fd);sockall[index]=0;sum--;printf("聊天室人數:%d\n",sum);return NULL; }int main(){pthread_t id;int fd=socket(AF_INET,SOCK_STREAM,0);//TCP協議if(fd==-1)perror("socket "),exit(-1);struct sockaddr_in addr;addr.sin_family=AF_INET; //協議簇addr.sin_port=htons(2222);addr.sin_addr.s_addr=inet_addr("192.168.1.112");/*防止復用*/int reuse=1;setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1)perror("bind "),exit(-1);printf("成功綁定端口\n");listen(fd,100);//設置等待隊列的長度int index;while(1){struct sockaddr_in from;socklen_t len=sizeof(from);int sockfd=accept(fd,(struct sockaddr*)&from,&len);index=socket_findsit();if(index!=-1){sockall[index]=sockfd;pthread_create(&id,0,task_chat,(void*)(index));}else{printf("聊天室人數已滿\n");}}}?
?
2、客戶端代碼
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> #include<pthread.h> void* task_read(void*p){int fd=(int)p;char buf[200]={};while(1){read(fd,buf,sizeof(buf));printf("%s\n",buf);memset(buf,0,strlen(buf));} }int main(){pthread_t id;int fd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1)perror("socket "),exit(-1);struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(2222);addr.sin_addr.s_addr=inet_addr("114.116.5.220");int res=connect(fd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1)perror("connect "),exit(-1);printf("成功連接服務器!\n");char buf1[100]={};char buf2[100]={};printf("請輸入您的用戶名:\n");scanf("%s",buf1);write(fd,buf1,strlen(buf1));memset(buf1,0,sizeof(buf1));printf("歡迎來到聊天室!\n");pthread_create(&id,0,task_read,(void*)fd);while(1){scanf("%s",buf1);write(fd,buf1,strlen(buf1));if(!strcmp(buf1,"bye"))break;memset(buf1,0,strlen(buf1));}close(fd); }?
?
3 基于UDP的一對多編程
UDP協議,用戶數據報協議,無需連接,消耗資源少,但是有可能出錯。
3.1 服務器編程步驟
1、socket(),得到socket描述符
2、準備通信地址,strutc sockaddr_in
3、綁定bind()
4、讀寫
5、關閉描述符
?
3.2 客戶端編程步驟
1、socket(),得到socket描述符
2、準備通信地址,strutc sockaddr_in
3、讀寫
4、關閉描述符
客戶端不需要綁定或者連接。
?
3.3 UDP下的讀寫數據
UDP協議下,使用的讀寫函數與TCP不同,在不連接的前提下,讀數據使用read()或者recvfrom(),read()只能讀數據,不能讀對方發送方的通信地址,而recvfrom兩者皆可。寫數據使用sendto(),不能使用write()。
?
3.4 函數解釋
1、recvfrom()
size_t recvfrom( int fd , void *buf , size_t len , int flags , struct sockaddr *src_addr , socklen_t *addrlen);參數解釋
fd 文件描述符,在這里就是socket描述符,buf是要將文件讀取進的對象,len是讀取的長度,flags一般給0即可,src_addr是一個傳出參數,用于接收發送方的通信地址,addrlen是一個傳入傳出參數,傳入addr的真實長度,傳出接收到的客戶端的通信地址的真實長度。
2、sendto()
int sendto(int fd , void *addr , size_t len , int flag , struct sockaddr *addr , socklen_t addlen);參數解釋
fd是文件描述符,addr是要寫入的內容指針,len是要寫入的長度,addr 傳入數據接收方的通信地址,addrlen就是通信地址的長度。
返回:成功返回發送的字節數,失敗返回-1。
?
3.5 基于UDP的時間服務器代碼
1、服務器代碼
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<time.h> #include<string.h>int main(){int fd=socket(AF_INET,SOCK_DGRAM,0);if(fd==-1)perror("socket "),exit(-1);struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(2222);addr.sin_addr.s_addr=inet_addr("192.168.1.112");int res=bind(fd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1)perror("bind "),exit(-1);printf("bind ok\n");char buf[100]={};while(1){struct sockaddr_in from;socklen_t len=sizeof(from);res=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,&len);pid_t pid=fork();if(!pid){time_t time1=time(NULL);struct tm* tm1=localtime(&time1);printf("讀到了%d字節,內容:%s\n",res,buf);memset(buf,0,sizeof(buf));sprintf(buf,"%d/%d/%d %d:%d:%d",tm1->tm_year+1900,tm1->tm_mon+1,tm1->tm_mday,tm1->tm_hour,tm1->tm_min,tm1->tm_sec);sendto(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,len);exit(0);}} }?
?
2、客戶端代碼
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h>int main(){int fd=socket(AF_INET,SOCK_DGRAM,0);if(fd==-1)perror("socket "),exit(-1);struct sockaddr_in addr;addr.sin_family=AF_INET;addr.sin_port=htons(2222);addr.sin_addr.s_addr=inet_addr("192.168.1.112");sendto(fd,"hello",5,0,(struct sockaddr*)&addr,sizeof(addr));char buf[100]={};int res=read(fd,buf,sizeof(buf));printf("日期:%s\n",buf);close(fd);return 0; }?
?
總結
- 上一篇: 页面回到顶部的几种方法
- 下一篇: Unix网络编程卷1学习总结