一. TCP的编程模型
案例1:
TCP的服务器(在案例中使用浏览器作为客户程序,访问服务器程序)
/// tcp_server.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 r;
struct sockaddr_in sadr; // 服务器地址
struct sockaddr_in cadr; // 用户地址
socklen_t len;
// 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("120.6.20.247",&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,5); // 队列中可以容纳未处理连接的最大数目
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,d端口:%u\n",
cfd,inet_ntoa(cadr.sin_addr),ntohs(cadr.sin_port));
// 5. 处理代理客户描述符号的数据
while(1)
{
char buf[1024+1];
r = recv(cfd,buf,1024,0);
//r = recvfrom(cfd,buf,100,0,0,0);
if(r>0)
{
buf[r] = 0;
printf("::%s\n",buf);
}
if(r==0)
{
printf("连接断开!\n");
break;
}
if(r==-1)
{
printf("网络故障!\n");
break;
}
}
close(cfd);
close(serverfd);
}
--------------------------------
$ netstat -tn ----------查看网络使用情况
打开浏览器
http://120.6.20.247:9999/index.html -------- 注意这里的IP地址,端口号与程序中要一致。
补充:
socket 建立服务器的文件描述符号缓冲
bind 把地址IP地址与端口设置到文件描述符号
listen 负责根据客户连接的不同IP与端口,生产对应的文件描述符号及其信息。
accept :一旦listen有新的描述符号产生就返回,否则阻塞。
二. TCP通信特点 (相对于UDP)
使用的是TCP协议的特点:
有连接: 只要连接后,发送数据不用指定IP 与 端口
数据无边界: TCP 数据流, 非数据报文。
描述符号双工。
数据准确:TCP 协议保证数据完全正确。
使用TCP发送数据注意:
不要以为固定的数据一定接收正确,要求使用MSG_WATIALL
recv(),对固定长度的数据使用MSG_WAITALL
MSG_WATIALL (将缓冲填满,使程序没有问题,这也是程序容易出现问题的原因)
案例(比较重要的一个案例):
使用TCP传送文件(首先在TCP传送中数据是没有边界的)
需要定义文件数据包
int 数据的大小
char[ ] 数据内容
传送文件名
传送数据
传送0长度的数据表示文件结束。
补充:
(1)send函数
ssize_t send(int sockfd, ///发送socket文件描述符
const void *buf, ///想要发送数据的数据缓冲
size_t len, /// 实际要发送的字节数
int flags); 一般置0
(2)IP的一些协议
$cd /etc/
#vi protocols
/// client.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()
{
int sfd;
int ffd;
int size;
int r;
char buf[128];
int len;
struct sockaddr_in dr; // 保存IP的
char filename[] = "udp_a.c";
/// 1. 建立socket
sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1) printf("socket err:%m\n"),exit(-1);
printf("建立socket成功!\n");
/// 2. 连接到服务器
dr.sin_family = AF_INET; // 地址协议
dr.sin_port = htons(9988); // 端口
inet_aton("120.6.20.247",&dr.sin_addr);
r = connect(sfd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1) printf("connect err:%m\n"),close(sfd),exit(-1);
printf("connect 成功!\n");
/// 3. 打开文件
ffd = open(filename,O_RDONLY);
if(ffd == -1) printf("open err:%m\n"),close(sfd),close(ffd),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("send err:%m\n"),close(ffd),close(sfd),exit(-1);
printf("发送文件名成功!\n");
// 5. 循环发送数据
while(1)
{
size = read(ffd,buf,128);
if(size == -1) break;
if(size == 0) break;
if(size > 0)
{ /// 发送一个数据包时,首先要发送数据包的长度,然后在发送数据,
r = send(sfd,&size,sizeof(size),0);// 发送数据长度
if(r == -1) break;
r = send(sfd,buf,size,0); // 发送数据
}
}
// 6. 读取到文件尾,发送0数据包
size = 0;
r = send(sfd,&size,sizeof(size),0);
close(ffd);
close(sfd);
printf("OK!\n");
}
// server.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()
{
int sfd,cfd,ffd;
int r;
int len;
char buf[128];
char filename[100];
struct sockaddr_in dr;
// 1. 建立服务器socket
sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1) printf("socket err:%m\n"),exit(-1);
printf("建立服务器成功!\n");
// 2.绑定IP 地址与端口
dr.sin_family = AF_INET;
dr.sin_port = htons(9988);
dr.sin_addr.s_addr = inet_addr("120.6.20.247");
r = bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
printf("绑定地址成功\n");
// 3. 监听
r = listen(sfd,10);
if( r == -1) printf("listen err:%m\n"),close(sfd),exit(-1);
printf("监听成功!\n");
// 4. 接收,连接
cfd = accept(sfd,0,0);
if(cfd == -1) printf("accept err:%m\n"),close(sfd),exit(-1);
printf("开始接收文件名...\n");
// 5. 接收文件名
r = recv(cfd,&len,sizeof(len),MSG_WAITALL);//接收文件名长度,保存在len中
printf("文件名长度:%d\n",len);
r = recv(cfd,filename,len,MSG_WAITALL);//接收文件名,保存在filename
filename[len] = 0;
printf("传递的文件是:%s\n",filename);
// 6 创建文件
ffd = open(filename,O_RDWR | O_CREAT,0666);//创建的文件名就是刚才接收保存在filename中的文件名
if(ffd == -1) printf("open err:%m\n"),close(sfd),close(ffd),exit(-1);
printf("创建文件成功!");
// 7. 循环接收文件
while(1)
{
r = recv(cfd,&len,sizeof(len),MSG_WAITALL);// 接收数据包的长度
if(len == 0) break; // 当长度为0时,接收完毕,退出
r = recv(cfd,buf,len,MSG_WAITALL);// 接收数据包数据内容
write(ffd,buf,len);// 把接收的数据包内容写入刚才创建的文件中
}
// 8. 关闭文件
close(ffd);
close(cfd);
close(sfd);
printf("接收文件成功!\n");
}
说明:
开两个终端,可以把服务器程序移到上级目录进行编译运行,
客户端程序在当前目录运行,可以用当前一个现有的文件进行传输。
注意:
客户端与服务器的IP地址与端口的一致性,这是能传输的关键因素。