linux高階程式設計day10 筆記 (轉)
回顧:
UDP模型的UML圖
TCP模型的UML圖
案例1:
TCP的伺服器(在案例中使用瀏覽器作為客戶程式)
socket建立伺服器的檔案描述符號緩衝
bind把IP地址與埠設定到檔案描述符號中
listen負責根據客戶連線的不同IP與埠,負責生成對應的檔案描述符號及其資訊
accept一旦listen有新的描述符號產生就返回,否則阻塞。
//tcpserver.c#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in
#include <arpa/inet.h>
main()
{
int serverfd;
int cfd;
int a;
struct sockaddr_in sadr;
struct sockaddr_in cadr;
socklen_t len;
int r;
char buf[1024];
//1.socket serverfd=socket(AF_INET,SOCK_STREAM,0);
if(serverfd==-1) printf("1:%m\n"),exit(-1);
printf("建立伺服器socket成功!\n");
//
sadr.sin_port=htons(9999);
inet_aton("192.168.180.92",&sadr.sin_addr);
r=bind(serverfd,
(struct sockaddr*)&sadr,sizeof(sadr));
if(r==-1) printf("2:%m\n"),exit(-1);
printf("伺服器地址繫結成功!\n");
//3.listen r=listen(serverfd,10);
if
printf("監聽伺服器成功!\n");
//4.accept len=sizeof(cadr);
cfd=accept(serverfd,
(struct sockaddr*)&cadr,&len); //每接受一個新的連線,就會返回一個新的檔案描述符,來分辨是哪個連線。 printf("有人連線:%d,IP:%s:%u\n",
cfd,inet_ntoa(cadr.sin_addr),
ntohs(cadr.sin_port));
//5.處理代理客戶描述符號的資料 while(1)
{
r=recv(cfd,&a,4,MSG_WAITALL);
if(r>0)
{
//buf[r]=0; printf("::%d\n",a);
}
if(r==0)
{
printf("連線斷開!\n");
break;
}
if(r==-1)
{
printf("網路故障!\n");
break;
}
}
close(cfd);
close(serverfd);
}
案例2:
每個客戶的代理描述符號的通訊
二.TCP通訊特點(相對於UDP)
案例3:
有連線:主要連線後,傳送資料不用指定IP與埠
資料無邊界:TCP資料流,非資料報文.
描述符號雙工:
資料準確:TCP協議保證資料時完全正確
案例4:
使用TCP傳送資料注意:
不要以為固定長的資料,一定接收正確,要求使用MSG_WAITALL(必須等待得到指定快取長度recv才返回)
案例5:
TCP資料傳送的分析:
定長資料:
基本資料int short long float double
結構體資料struct
建議使用MSG_WAITALL
不定長資料:
字串資料以及檔案資料等不固定長度的資料怎麼傳送?
制定資料包:
頭:大小固定(資料大小)
體:大小變化(資料)
案例6:
使用TCP傳送檔案
定義檔案資料包.
int 資料大小;
char[]資料
傳遞檔名
傳遞資料(迴圈)
傳遞0長度的資料表示檔案結束
程式碼如下:
//demo1Client.c
//傳送端的程式碼#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
main()
{
//1. 建立socket
//2. 連線到伺服器
//3. 開啟檔案
//4. 傳送檔名
//5. 迴圈傳送檔案
//6. 讀取到檔案尾,傳送0資料包 int sfd; //socket描述符 int ffd; //檔案描述符 int size; //讀取和傳送檔案的長度 int r; //函式返回值
int len; //要傳送的檔名的長度 char buf[128]; //資料的快取
struct sockaddr_in dr; //網路地址 char filename[]="udp_a.c"; //檔名
//1.建立socket sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
printf("1:%m\n"),exit(-1);
printf("socket成功!\n");
//2.連線到伺服器 dr.sin_family=AF_INET;
dr.sin_port=htons(9988);
inet_aton("192.168.180.92",&dr.sin_addr);
r=connect(sfd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1)
printf("2:%m\n"),close(sfd),exit(-1);
printf("connect成功!\n");
//3.開啟檔案 ffd=open(filename,O_RDONLY);
if(ffd==-1)
printf("3:%m\n"),close(sfd),exit(-1);
printf("open檔案成功!\n");
//4.傳送檔名 len=strlen(filename);
r=send(sfd,&len,sizeof(len),0);//傳送檔名長度(告訴流,檔名佔多長) r=send(sfd,filename,len,0);//傳送檔名 if(r==-1)
printf("4:%m\n"),close(ffd),close(sfd),exit(-1);
printf("傳送檔名成功!\n");
//5.迴圈傳送資料 while(1)
{
size=read(ffd,buf,128);
if(size==-1) break; //read錯誤,跳出迴圈 if(size==0) break; //讀到檔案尾,跳出迴圈 if(size>0)
{
//先發送資料的長度,再發送資料
//傳送資料長度 r=send(sfd,&size,sizeof(size),0);
if(r==-1) break;
r=send(sfd,buf,size,0);//傳送資料 if(r==-1) break;
}
}
//6.讀取到檔案尾,傳送0資料包 size=0;
r=send(sfd,&size,sizeof(size),0);
close(ffd);
close(sfd);
printf("OK!\n");
}
//demo1server.c
//接收伺服器的程式碼#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
main()
{
//1. 建立伺服器socket
//2. 繫結IP地址與埠
//3. 監聽
//4. 接收連線
//5. 接收檔名
//6. 建立檔案
//7. 迴圈接收檔案資料
int sfd, cfd, ffd;
int r;
int len;
char buf[128]; //傳送端定義的快取大小是128,這裡最好不要小於128 char filename[100];
struct sockaddr_in dr;
//1. 建立伺服器socket sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1)
printf("1:%m\n"), exit(-1);
printf("socket伺服器建立成功!\n");
//2. 繫結IP地址與埠 dr.sin_family = AF_INET;
dr.sin_port = htons(9988);
inet_aton("192.168.180.92", &dr.sin_addr); //dr.sin_addr.s_addr = inet_addr("192.168.180.92");注意區別 r = bind(sfd, (struct sockaddr*)&dr, sizeof(dr));
if(r == -1)
printf("2:%m"), close(sfd), exit(-1);
printf("繫結地址成功!\n");
//3. 監聽 r = listen(sfd, 10);
if(r == -1)
printf("3:%m\n"), close(sfd), exit(-1);
printf("監聽成功!\n");
//4. 接收連線 cfd = accept(sfd, 0, 0); //這裡我們不關心傳送端的IP等資訊,所以後面兩個引數都為0。這裡返回一個新的描述符,代表接收的連線 if(cfd == -1)
printf("4:%m\n"), close(sfd), exit(-1);
printf("開始接收檔案!\n");
//5. 接收檔名 r = recv(cfd, &len, sizeof(len), MSG_WAITALL); //接收檔名的長度 r = recv(cfd, filename, len, MSG_WAITALL); //根據檔名長度,接收檔名 filename[r] = 0; //在檔名後面加結束符
//6. 建立檔案 ffd = open(filename, O_CREAT|O_RDWR, 0666); //如果檔案存在,直接覆蓋.不要和傳送檔案放在同一個目錄執行,會覆蓋傳送檔案 if(ffd == -1)
printf("6:%m\n"), close(sfd), close(cfd), exit(-1);
printf("建立檔案成功!\n");
//7. 迴圈接收檔案資料 while(1)
{
r = recv(cfd, &len, sizeof(len), MSG_WAITALL);
if(len == 0)
break; //長度為0,表示檔案傳送完畢的訊號 r = recv(cfd, buf, len, MGS_WAITALL);
r = write(ffd, buf, len);
}
close(ffd);
close(cfd);
close(sfd);
printf("接收資料完畢!\n");
}
PS:UDP面向無連線,TCP面向連線,所以推薦UDP不用connect,直接sendto, 而TCP則先連線,然後send,而不是sendto。
三.TCP伺服器程式設計模式
TCP的伺服器端維護多個客戶的網路檔案描述符號.
對伺服器多個客戶描述符號同時做讀操作,是不可能.需要多工模型完成.
多工模型?
1.多程序
2.IO的非同步模式(select模式/poll模式)
3.多執行緒模式
4.多程序池
5.執行緒池
四.綜合應用--多程序應用
1.怎樣使用多程序
2.多程序的缺陷,以及怎麼解決
小例子:用TCP寫一個聊天程式
客戶端
2.1.建立socket
2.2.連線伺服器
2.3.建立CURSES介面
2.4.建立子程序
2.5.在父程序中,輸入,傳送聊天資訊
2.6.在子程序中,接收伺服器傳遞其他客戶聊天資訊
//chatclient.c
//聊天程式客戶端#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <curses.h>
#include <signal.h>
WINDOW*winfo,*wmsg;
int fd;
int r;
struct sockaddr_in dr;
int isover=1;
int initSocket(); //初始化:建立描述符,繫結IPvoid initUI(); //初始化curses介面void destroy(); //清理:釋放UI, 關閉網路void handle(int s)
{
int status;
wait(&status);
destroy();
exit(-1);
}
main()
{
//printf("網路初始化成功!\n"); initUI();
r=initSocket();
if(r==-1) exit(-1);
signal(SIGCHLD,handle);
if(fork())
{
//父程序,輸入,傳送 char buf[256];
while(1)
{
mvwgetstr(wmsg,1,1,buf);
//buf[r]=0; send(fd,buf,strlen(buf),0);
//wclear(wmsg);
//box(wmsg,0,0); refresh();
wrefresh(wmsg);
wrefresh(winfo);
}
}
else
{
//子程序,接收,顯示 char buf[256];
int line=1;
while(1)
{
r=recv(fd,buf,255,0);
if(r==-1) break;
if(r==0) break;
buf[r]=0;
mvwaddstr(winfo,line,1,buf);
line++;
if(line>=(LINES-3))
{
wclear(winfo);
line=1;
box(winfo,0,0);
}
wmove(wmsg,1,1);
touchwin(wmsg);
refresh();
wrefresh(winfo);
wrefresh(wmsg);
}
exit(-1);
}
destroy();
}
void destroy()
{
close(fd);
endwin();
}
void initUI()
{
initscr();
winfo=derwin(stdscr,(LINES-3),COLS,0,0);
wmsg=derwin(stdscr,3,COLS,LINES-3,0);
keypad(stdscr,TRUE);
keypad(wmsg,TRUE);
keypad(winfo,TRUE);
box(winfo,0,0);
box(wmsg,0,0);
refresh();
wrefresh(winfo);
wrefresh(wmsg);
}
int initSocket()
{
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1) return -1;
dr.sin_family=AF_INET;
dr.sin_port=htons(9989);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1)
{
close(fd);
return -1;
}
return 0; //fd是全域性變數,不用返回。初始化成功,返回0}
//chatserver.c
//聊天程式伺服器端#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mman.h>
int sfd;
int *fds;//存放所有客戶代理描述符號int idx=0;//客戶在陣列中下標struct sockaddr_in dr;
int r;
main()
{
//1. 建立伺服器socket
//2. 繫結地址
//3. 監聽
//4. 迴圈接收客戶連線
//5. 建立一個子程序
//6. 子程序任務:接收客戶資料並且廣播
//1.建立伺服器 socket fds=mmap(0,4*100,PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,0,0);
bzero(fds,sizeof(fds));
sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1) printf("1:%m\n"),exit(-1);
printf("socket OK!\n");
//2.繫結地址 dr.sin_family=AF_INET;
dr.sin_port=htons(9989);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1) printf("2:%m\n"),exit(-1);
printf("bind ok!\n");
//3.監聽 r=listen(sfd,10);
if(r==-1) printf("3:%m\n"),exit(-1);
printf("listen ok!\n");
//4.迴圈接收客戶連線 while(1)
{
fds[idx]=accept(sfd,0,0);
if(fds[idx]==-1) break;
printf("有客戶連線:%d\n",fds[idx]);
//5.建立一個子程序 if(fork())
{
idx++;
continue;
}
else
{
//6.子程序任務:接收客戶資料並且廣播 char buf[256];
int i;
printf("開始接收客戶資料:%d\n",fds[idx]);
while(1)
{
//接收客戶資料 r=recv(fds[idx],buf,255,0);
printf("%d\n",r);
if(r==0)
{
printf("有客戶退出\n");
close(fds[idx]);
fds[idx]=0;
break;
}
if(r==-1)
{
printf("網路故障\n");
close(fds[idx]);
fds[idx]=0;
break;
}
buf[r]=0;
printf("來自客戶的資料:%s\n",buf);
//廣播 for(i=0;i<100;i++)
{
if(fds[i]>0)
{
send(fds[i],buf,r,0);
}
}
}
exit(0);
}
}
close(sfd);
}
總結:
建立socket
繫結地址
監聽
迴圈接收客戶連線
為客戶建立子程序
在子程序接收該客戶的資料,並且廣播
總結:
1.TCP的四大特點
2.TCP的資料接收:固定長與變長資料的接收
3.TCP的伺服器多程序處理
問題:多程序由於程序資源結構獨立.
新程序的檔案描述符號的環境在老程序無法訪問?
作業:
思考:
有什麼程式設計技巧可以解決程序的檔案描述符號的一致?
作業:
完成TCP的聊天程式.
1.資料能執行
2.處理僵死程序
3.伺服器退出,客戶也能正常結束
4.客戶退出,伺服器也能夠正確結束客戶連線.