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