网络编程整理
网络编程
1.TCP/UDP特点
1.TCP
1.面向连接(流式套接字 SOCK_STREAM)
2.数据完整安全,可靠,有序
PS:数据完整不丢失,有序 ,数据完整采用CRC循环冗余校验,数据不丢失采用重传机制和超时机制,有序的发送端的拆分编号,接收端排序组合。若在某一段时间内接收端到的为两个一样的数据,则选择丢弃其中的一个。发送者每发送一个tcp段,启动一个定时器,超时没有收到ACK,则重发。而接受者每收到一个tcp段,则发送一个ACK消息。
3.慢 (效率低,传输慢)
2.UDP
1.面向数据报(数据报套接字 SOCK_DGRAM)
2.数据可能会丢失,乱序
3.快
流式套接字:
1.数据无界限 (连续发送的多个数据之间无界限)
数据报套接字:
1.数据有明确界限
PS:针对流式套接字存在的数据之间无界限的问题要解决的方法:
场景建立:如果发送一个文件,首先要发送的是文件名,文件大小,文件内容。例如
if(writen(connfd,FILENAME,LEN_FILENAME)<0)
ERR("write failed");
//这儿的文件名为字符串,是属于字符数组,char[]无需转为网络序。
int filesize_nbo = htonl(filesize);
if(writen(connfd,&filesize_nbo,LEN_FILESIZE)<0)
ERR("write failed");
看网络状况,状况好的情况下,发送的快,则其中间隔较大,若网络不好的情况下,第二次与第一次的间隔较小,都将视为第一次发送的东西。解决办法,规定LEN_FILENAME,LEN_FILESIZE,即使文件名放不满,也要写LEN_FILENAME的这么多的数据过去。这时就要采用writen 保证确实写了如此多的数据过去。保证数据完整性。
2.网络中的数据传输
预备知识:字节序(大小端)
大端(big-endian):高地址>>>>低字节 低地址>>>高字节
小端(little--endian):高地址>>>高字节 低地址>>>低字节
1.单字节数据(char,char[],)
2.多字节数据(short,int,long)
发送之前:HBO(host byteorder) --> NBO(network byteorder)
接收之后:NBO --> HBO
htonl 32bit
ntohs 16bit
ntohl 32bit
3.结构体数据
1.成员要进行字节序转换
2.强制按照1字节补齐(即为不补齐) 加入push pop将结构体限制为push pop的范围之内。
#pragma pack (push)
#pragma pack (1)
struct A{
int a;
char c;
short b;
};
#pragma pack (pop)
struct B{};
4.浮点型数据的发送
整数,小数分开发
5.负数
整数
负数符号单独发
3.地址的表示方法(IP + Port)
IP
ipv4(32bit)/ipv6(128bit)
表示法:
字符串 "192.168.2.100"
数字 192<<24 | 168<<16 | 2<<8 | 100;
struct in_addr
{
in_addr_t s_addr; //NBO
};
可以用这种方法赋值:in_addr.s_addr=192<<24|168<<16|2<<8|100
inet_ntop();
inet_pton();
PORT
16bit(0-65535) htons来将转化为网络序
0-1023 //周知端口,特权端口(只有root权限才能使用)
1023-
IPV4地址结构
struct sockaddr_in
{
sin_family_t sin_family //地址族(ipv4 or ipv6)
unsigned short sin_port //NBO 端口
struct in_addr sin_addr //NBO IP
char sin_zero[8] //保留
};
struct sockaddr_in6
{
//...
};
//通用地址结构
struct sockaddr
{
};
4.TCP编程
SOCK_STREAM(一般专指TCP)
SOCK_DGRAM (一般专指UDP)
SOCK_RAW(原始套接字,用以访问底层接口)
协议可以使用getprotobyname获取协议值,socket(PF_INET,SOCK_STREAM,0)其中0为协议值。默认为0仅限于TCP UDP,原始套接字编程要使用这个函数获得协议值。
1.服务器端
1.创建socket
socket
2.绑定地址
bind
3.监听
listen
while(1)
{
4.接收连接(阻塞)
accept
5.通信(多进程,多线程等模型)
read/write
6.关闭与当前客户端的连接
close/shutdown
}
7.关闭服务器
2.客户端
1.创建socket
socket
2.和对方建立连接
connect
3.通信
read/write
4.关闭连接
close/shutdown
5.udp编程
1.服务器端
1.创建socket
socket(PF_INET,SOCK_DGRAM,0);
2.绑定地址
bind(sockfd,(struct sockaddr*)&ipv4,sizeof(ipv4));
3.交互
read/write
sendto(sockfd,buf,size,0,const (struct sockaddr*)&peer,sizeof(peer))
recvfrom(sockfd,buf,size,0,struct sockaddr*,socklen_t *len)
4.关闭
close
2.客户端
1.创建socket
socket(PF_INET,SOCK_DGRAM,0);
2.绑定地址(可选)
bind(sockfd,(struct sockaddr*)&ipv4,sizeof(ipv4));
3.交互
read/write
sendto(sockfd,buf,size,0,const (struct sockaddr*)&peer,sizeof(peer))
recvfrom(sockfd,buf,size,0,struct sockaddr*,socklen_t *len)
4.关闭
close
数据有明确界限
5.udp广播
1.IP地址
网络号 + 主机号
网络号 + 子网号 + 主机号
2.广播地址
1.受限广播地址(所有路由器都不会转发目标为4个255的数据)
255.255.255.255
A
B
C
D
E
2.网内广播
子网号,主机号 全为1
3.子网内广播
主机号全为1
192.168.3.10/28的广播地址:192.168.3.15
3.广播编程
1.设置允许发送广播消息
setsockopt(int sockfd,int level,int optname,void *val,size_t len);
int val = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val));
2.发送目标地址为广播地址
6.udp组播
组播地址:
D类IP 224.0.0.1~239.255.255.255
1.发送者
发给一个组播地址
2.接收者(组播编程主要对接受者有限定)
1.加入组
struct ip_mreq{
struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */
};
struct ip_mreq mreq;
};
struct ip_mreq mreq;
setsockopt (socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
2.接收消息
3.离开组
setsockopt (socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
7.广播组播注意事项:
1.设置缺省网关
route -n
route add default gw 192.168.5.1
2.防火墙
service iptables stop/start/restart/status
//=============================================================
1.网络数据库
#include <netdb.h>
gethostbyname --> 域名解析 /etc/hosts /etc/resolv.conf
getprotobyname --> /etc/protocols
2.设置网络选项
setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen);
getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen);
level:
SOL_SOCKET 通用选项
SO_BROADCAST
SO_LINGER
SO_RCVBUF,SO_SNDBUF
SO_RCVLOWAT , SO_SNDLOWAT 低潮值
SO_REUSEADDR, SO_REUSEPORT
IPPROTO_IP ipv4选项
IP_TTL(time-to-live)
IP_ADDMEMBERSHIP
IP_DROPMEMBERSHIP
IPPROTO_IPV6 ipv6选项
IPPROTO_ICMPV6 icmpv6选项
IPPROTO_TCP tcp选项
3.IO模型
1.阻塞模型
2.非阻塞模型
fcntl(O_NONBLOCK);
if(read(...)<0)
{
if(errno==EGAIN)
{
//do other...
}
}
3.信号驱动IO sigio
4.异步IO
5.IO多路复用
Select:用于监视多个文件模型
网络编程注意事项:
1.优雅关闭问题
1.问题提出
发送者发送完毕,立即关闭连接,并且退出程序,此时接收者可能没有收完。(退出程序,即为原先发送者发送的数据存在缓冲区中,一旦程序退出,缓冲区则会被销毁,自然接受者收到的信息就不完全。)
2.解决
发送者:
1.发送完毕
2.shutdown(sockfd,SHUT_WR); //刷新缓冲 //shutdown可以关闭写 (TCP为全双工模型)
3.read(sockfd,&c,1); //阻塞等待接受者发送一个消息给发送者,告诉发送者我已经发送完毕。此时有一种情况,若接受者没有发送信息给发送者,则read函数得到的为0,意味着接受者已关闭连接,则发送者也会因此而解除阻塞。
返回0
4.shutdown(sockfd,SHUT_RD); //关闭读连接
接收者:
1.接收完毕
2.close(sockfd);
2.设置地址重用
socket之后,bind之前设置
int val = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))
2*msl 个时间断
3.数据传输
多字节
HBO-->NBO
NBO-->HBO
结构体
1字节补齐
多字节成员
...
4.tcp数据界限问题:读取写入固定字节,当然要采用readn writen保证数据的完整性。
readn
written
int readn(int fd,void *buf,size_t len)
{
int total = 0,n;
while(total<len)
{
again:
if((n = read(fd,buf+total,len-total))<0)
{
if(errno==EINTR)
goto again;
return -1;
}
else if(n==0)
break;
total += n;
}
return total;
}
5.MTU(max-transmition-unit),路径MTU
局域网:<1024
外网:<512