网络编程模型
c/s 模型:客户端服务器模型
b/s 模型:浏览器服务器模型
1.tcp网络流程
服务器流程:
1.创建套接字 2.完善服务器网络信息结构体 3.绑定服务器网络信息结构体 4.让服务器处于监听状态 5.accept阻塞等待客户端连接信号 6.收发数据 7.关闭套接字
客户端流程:
1.创建套接字 2.连接connect 3.收发数据 4.关闭套接字
2.函数
2.1socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:domain:通信域
Name Purpose Man page
AF_UNIX,AF_LOCAL Local communication(本地通信) unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_PACKET 原始套接字 packet(7)
type:套接字的类型
SOCK_STREAM TCP
SOCK_DGRAM UDP使用
SOCK_RAW 原始套接字使用
protocol:附加文件,没有写0
返回值:成功:创建的新套接字(文件描述符)
失败:-1 重置错误码
2.2bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定套接字和网络信息结构体
参数:sockfd:socket函数产生的套接字文件描述符(socket函数返回值)
addr:避免冲突警告
2.3listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);产生半连接队列
功能:将套接字设置成被动监听状态,只有这个状态的套接字才能等待客户端连接
参数:sockfd:socket函数返回值
backlog:半连接队列的长度,一般传 5 10 都行 不是0就行
返回值:成功:0
失败:-1 重置错误码
accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);产生全连接队列,返回值创 建的文件描述符
功能:提取半连接队列中的第一个客户端连接,成功,放到函数产生的全连接队列中,专门 用于和当前客户端通信,返回的套接字和原套接字类型相同
参数:sockfd:listen后的sockfd
addr:客户端的网络信息结构体首地址,不关心写NULL
addrlen:客户端addr长度,不关心写NULL
返回值:成功:创建的新的文件描述符 专门用于和当前客户端通信
失败:-1 重置错误码
connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:与服务器建立连接
参数:sockfd:accept函数返回值
addr:服务器的网络信息结构体
addrlen:addr的长度
返回值:成功:0
失败:-1 重置错误码
服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
perror(msg);\
exit(-1);\
}while(0)
int main(int argc, char const *argv[])
{
//1.创建套接字
int sockfd=0;//需要接函数返回值,后面函数会用
if(-1==(sockfd=socket(AF_INET,SOCK_STREAM,0))){
ERRLOG("socket error");
}
//2.服务器网络信息结构体填充
//struct sockaddr addr;
struct sockaddr_in ad;
memset(&ad, 0, sizeof(ad));
ad.sin_family=AF_INET;
ad.sin_port=htons(7788);//自己随意指定,不冲突就行,冲突就换
ad.sin_addr.s_addr=inet_addr("192.168.250.100");
//3.绑定套接字和服务器网络信息结构体
if(-1==bind(sockfd,(struct sockaddr*)&ad,sizeof(ad))){
ERRLOG("bind error");
}
//4.让套接字处于被动监听状态
if(-1==listen(sockfd,5)){
ERRLOG("listen error");
}
//5.阻塞等待客户端连接
printf("正在等待客户端发来信息\n");
int newfd=0;
if((newfd=accept(sockfd,NULL,NULL))==-1){
ERRLOG("accept error");
}
printf("客户端连接成功\n");
//6.收发数据
char buff[128]={0};
int lnum=0;
char opr=0;
int rnum=0;
int result=0;
read(newfd,buff,128);
printf("客户端发来数据:%s\n",buff);
sscanf(buff,"%d%c%d",&lnum,&opr,&rnum);
switch(opr){
case '+':
result=lnum+rnum;
break;
case '-':
result=lnum-rnum;
break;
case '*':
result=lnum*rnum;
break;
case '/':
result=(float)lnum/(float)rnum;
break;
}
memset(buff,0,128);
sprintf(buff,"result=%d",result);
write(newfd,buff,128);
//7.关闭套接字
close(sockfd);
close(newfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#define ERRLOG(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
perror(msg);\
exit(-1);\
}while(0)
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd=0;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
ERRLOG("socket error");
}
//填充服务器结构体
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(6666);
addr.sin_addr.s_addr=inet_addr("192.168.2.84");
//连接服务器
if(connect(sockfd,(struct sockaddr *)&addr,sizeof(addr))==-1){
ERRLOG("connect error");
}
//收发数据
char buff[128]={0};
//写入的数据从终端获取
fgets(buff,128,stdin);
buff[strlen(buff)-1]='\0';
write(sockfd,buff,128);
memset(buff,0,128);
read(sockfd,buff,128);
printf("服务器发来的信息是:%s\n",buff);
//7.关闭套接字
close(sockfd);
return 0;
}
send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:sockfd:accept函数返回值
buf:要发送的数据的缓冲区首地址
len:要发送数据的长度
flags:发送的标志位,0用法和write就一样了,MSG_DONTWAIT 表示非阻塞
返回值:成功:实际发送的字节数
失败:-1 重置错误码
注意:
如果对方关闭了套接字或者断开连接
第一次send没有反应,第二次send会产生SIGPIPE信号
下面三种用法是等价的:
write(sockfd, buf, 128);
send(sockfd, buf, 128, 0);
sendto(sockfd, buf, 128, 0, NULL, NULL);
recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:accept函数返回值
buf:要接收的数据的缓冲区首地址
len:要接收数据的长度
flags:发送的标志位,flags:接收的标志位,0用法和read就一样了, MSG_DONTWAIT 表示非阻塞
返回值:成功:实际接收的字节数
失败:-1 重置错误码
注意: 如果对方关闭了套接字或者断开连接 recv 会返回0
下面三种用法是等价的:
read(sockfd, buf, 128);
recv(sockfd, buf, 128, 0);
recvfrom(sockfd, buf, 128, 0, NULL, NULL);
粘包问题产生原因
tcp在传输的时候不是一调用send就直接将数据发送给客户端的,而是将数据放到发送缓冲区里
tcp底层的nagle算法,会将一定短时间内发送的数据包组装成一个整体,发送给对方
而接受方无法区分消息的边界和类型,就可能会导致有冲突的情况发生
也就是说,当文件只剩最后一部分的时候,很有可能将最后的结束字符和文件最后一部分作为一个整体发送给接收方,也有可能将三个128和最后的30+12一起发给接收方,但不管是哪种方式,接收方写入的时候是以128为单位接受的,所以接收方缓冲区里是无法单独比较over的,所以会产生粘包问题。
解决方法:
1.既然是一定短时间内的数据成为一个整体,那么最后发送的over就不和前面一块发送了,在over之前加一个延时函数,让他单独发送
但是不常用,因为服务器里禁止使用sleep
2.发送定长的数据包,
解决方法一代码