一.TCP的編程模型 回顧: 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.h>
#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");
//2.bind
sadr.sin_family=AF_INET;
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(r==-1) printf("3:%m\n"),exit(-1);
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(); //初始化:創建描述符,綁定IP
void 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.客戶退出,服務器也能夠正確結束客戶連接.