Internet-“冷战”的产物
1957年10月和11月,前苏联先后有两颗“Sputnik”卫星上天
1958年美国总统艾森豪威尔向美国国会提出建立DARPA (Defense Advanced Research Project Agency),即国防部高级研究计划署,简称ARPA
1968年6月DARPA提出“资源共享计算机网络” (Resource Sharing Computer Networks),目的在于让DARPA的所有电脑互连起来,这个网络就叫做ARPAnet,即“阿帕网”,是Interne的最早雏形
早期的ARPAnet使用网络控制协议(Network Control Protocol,NCP),不能互联不同类型的计算机和不同类型的操作系统,没有纠错功能
1973年由 Robert Kahn 和Vinton Cerf两人合作为ARPAnet开发了新的互联协议。
1974年12月两人正式发表第一份TCP协议详细说明,但此协议在有数据包丢失时不能有效的纠正
TCP协议分成了两个不同的协议:
用来检测网络传输中差错的传输控制协议TCP
专门负责对不同网络进行互联的互联网协议IP
从此,TCP/IP协议诞生
1983年ARPAnet上停止使用NCP,互联网上的主机全部使用TCP/IP协议。TCP/IP协议成为Internet中的“世界语”
网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。
每层实现不同的功能,其内部实现方法对外部其他层次来说是透明1的。每层向上层提供服务,同时使用下层提供的服务
网络体系结构即指网络的层次结构和每层所使用协议的集合
两类非常重要的体系结构:OSI与TCP/IP
OSI模型相关的协议已经很少使用,但模型本身非常通用
OSI模型是一个理想化的模型,尚未有完整的实现
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
适用情况:
适合于对传输质量要求较高,以及传输大量数据的通信。
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用情况:
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
适合于广播/组播式通信中。
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
socket是一个应用编程的接口,
它是一种特殊的文件描述符(对它执行IO的操作函数,比如,read(),write(),close()等操作函数)
并不仅限于TCP/IP协议
面向连接 (Transmission Control Protocol - TCP/IP)
无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)
socket代表着网络编程的一种资源
socket的类型:
流式套接字(SOCK_STREAM): 唯一对应着TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM): 唯一对应着UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW):(对应着多个协议,发送穿透了传输层)
可以对较低层次协议如IP、ICMP直接访问。
IP地址是Internet中主机的标识
Internet中的主机要与别的机器通信必须具有一个IP地址
IP地址为32位(IPv4)或者128位(IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
表示形式:常用点分形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。
IPV4:采用32位的整数来表示
IPV6:采用了128位整数来表示
mobileIPV6: local IP(本地注册的IP),roam IP(漫游IP)
IPV4地址:
点分形式: 192.168.7.246
32位整数
特殊IP地址:
局域网IP: 192.XXX.XXX.XXX 10.XXX.XXX.XXX
广播IP: xxx.xxx.xxx.255, 255.255.255.255(全网广播)
组播IP: 224.XXX.XXX.XXX~239.xxx.xxx.xxx
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
TCP端口号与UDP端口号独立
端口号一般由IANA (Internet Assigned Numbers Authority) 管理
周知端口:1~1023(1~255之间为常用周知端口,256~1023端口通常由UNIX系统占用)
注册端口:1024~49150
动态或私有端口:49151~65535
16位的数字(1-65535)
众所周知端口: 1~1023(FTP: 21,SSH: 22, HTTP:80, HTTPS:469)
保留端口: 1024-5000(不建议使用)
可以使用的:5000~65535
TCP端口和UDP端口是相互独立的
网络里面的通信是由 IP地址+端口号 来决定
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
小端序(little-endian) - 低序字节存储在低地址
将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
大端序(big-endian)- 高序字节存储在低地址
将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
网络中传输的数据必须按网络字节序,即大端字节序
在大部分PC机上,当应用进程将整数送入socket前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化成小端字节序
网络字节序(NBO - Network Byte Order)
主机字节序(HBO - Host Byte Order)
字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端问题
如CPU访问的是字符串,则不存在大小端问题
大端(Big-Endian):字节的高位在内存中放在存储单元的起始位置
X86/ARM: 小端
powerpc/mips, ARM作为路由器时,大端模式
网络传输的时候采用大端模式
#include
//将strptr所指的字符串转换成32位的网络字节序二进制值
int inet_aton(const char *strptr, struct in_addr *addrptr);
//功能同上,返回转换后的地址。
in_addr_t inet_addr(const char *cp);
cp: 点分形式的IP地址,结果是32位整数(内部包含了字节序的转换,默认是网络字节序的模式)
特点:
1. 仅适应于IPV4
2. 当出错时,返回-1
3.此函数不能用于255.255.255.255的转换
#include
int inet_pton(int af, const char *src, void *dst); //将点分十进制的ip地址转化为用于网络传输的数值格式
特点:
1.适应于IPV4和IPV6
2.能正确的处理255.255.255.255的转换问题
参数:
1.af: 地址协议族(AF_INET或AF_INET6)
2.src:是一个指针(填写点分形式的IP地址[主要指IPV4])
3.dst: 转换的结果给到dst
RETURN VALUE
inet_pton() returns 1 on success (network address was successfully con‐
verted). 0 is returned if src does not contain a character string representing a valid network address in the specified address family. If af does not contain a valid address family, -1 is returned and errno is set to EAFNOSUPPORT
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
连接测试:nc
socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听端口
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字
1.1 参数:
1.domain:
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_NETLINK Kernel user interface device netlink(7)
AF_PACKET Low level packet interface packet(7)
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
2.type:
SOCK_STREAM: 流式套接字 唯一对应于TCP
SOCK_DGRAM: 数据报套接字,唯一对应着UDP
SOCK_RAW: 原始套接字
3.protocol: 一般填0,原始套接字编程时需填充
1.2 返回值:
RETURN VALUE
On success, a file descriptor for the new socket is returned. On
error, -1 is returned, and errno is set appropriately.
成功时返回文件描述符,出错时返回为-1
2.1 参数:
sockfd: 通过socket()函数拿到的fd
addr: struct sockaddr的结构体变量的地址
addrlen: 地址长度
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
通用地址结构
struct sockaddr
{
u_short sa_family; // 地址族, AF_xxx
char sa_data[14]; // 14字节协议地址
};
//Internet协议地址结构
struct sockaddr_in
{
u_short sin_family; // 地址族, AF_INET,2 bytes
u_short sin_port; // 端口,2 bytes
struct in_addr sin_addr; // IPV4地址,4 bytes
char sin_zero[8]; // 8 bytes unused,作为填充
};
IPv4地址结构
// internet address
struct in_addr
{
in_addr_t s_addr; // u32 network address
};
如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
1.定义一个struct sockaddr_in类型的变量并清空
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));
2.填充地址信息
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
3.将该变量强制转换为struct sockaddr类型在函数中使用
bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
unsigned long inet_addr(char *address);
address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址。如果字符串包含的不是合法的IP地址,则函数返回-1。例如:
struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");
char* inet_ntoa(struct in_addr address);
将32位网络字节序二进制地址转换成点分十进制的字符串,address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL
int listen (int sockfd, int backlog);
sockfd:监听连接的套接字
backlog
指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5, 测试得知,ARM最大为8。内核中服务器的套接字fd会维护2个链表:
完成listen()调用后,socket变成了监听socket(listening socket)
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
sockfd : 监听套接字 ,经过前面socket()创建并通过bind(),listen()设置过的fd头文件
addr : 对方地址,获取连接过来的客户的信息
addrlen:地址长度,获取连接过来的客户的信息
返回值:已建立好连接的套接字或-1,RETURN VALUE
On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On
error, -1 is returned, and errno is set appropriately.
成功时返回已经建立好连接的新的newfd
listen()和accept()是TCP服务器端使用的函数
connect()函数和服务器的bind()函数写法类似,connect()是客户端使用的系统调用。
#include
#include
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
sockfd : socket返回的文件描述符,即通过socket()函数拿到的fd
serv_addr : 服务器端的地址信息 ,struct sockaddr的结构体变量的地址
addrlen : serv_addr的长度
返回值:0 或 -1
RETURN VALUE
If the connection or binding succeeds, zero is returned. On error, -1
is returned, and errno is set appropriately
#include
ssize_t recv(int socket, const void *buffer, size_t length, int flags);
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)
flags:
一般填写0,此时和read()作用一样
特殊的标志:
MSG_DONTWAIT: Enables nonblocking operation; 非阻塞版本
MSG_OOB:用于发送TCP类型的带外数据(out-of-band)
MSG_PEEK:
This flag causes the receive operation to return data from
the beginning of the receive queue without removing that
data from the queue. Thus, a subsequent receive call will
return the same data.
返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
#include
ssize_t send(int socket, const void *buffer, size_t length, int flags);
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)
send()比write多一个参数flags:
flags:
一般填写0,此时和write()作用一样
特殊的标志:
MSG_DONTWAIT: Enables nonblocking operation; 非阻塞版本
MSG_OOB:用于发送TCP类型的带外数据(out-of-band)
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好,使用read()/write()和recv()/send()时最好统一
int close(int sockfd);
关闭双向通讯
int shutdown(int sockfd, int howto);
TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
针对不同的howto,系统会采取不同的关闭方式。
shutdown()的howto参数
howto = 0
关闭读通道,但是可以继续往套接字写数据。
howto = 1
和上面相反,关闭写通道。只能从套接字读取数据。
howto = 2
关闭读写通道,和close()一样
#ifndef _MAKEU_NET_H_
#define _MAKEU_NET_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.3.59"
#define BACKLOG 5
#define QUIT_STR "quit"
#endif
#include "net.h"
int main(int argc, char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
//1.创建socket fd
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
//2.绑定
//2.1填充struct sockaddr_in结构体变量
bzero(&sin,sizeof(sin));//置零结构体变量
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 0
sin.sin_addr.s_addrs =inet_addr(SERV_IP_ADDR);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void*)&sin.sin_addr.s_addr) != 1){
perror("inet_pton");
exit(1);
}
#endif
//2.2绑定
if( bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("bind");
exit(1);
}
//3.调用listen()把主动套接字变成被动套接字
if(listen(fd,BACKLOG)<0){
perror("listen");
exit(1);
}
int newfd = -1;
//4.阻塞等待客户端连接请求
newfd = accept(fd,NULL,NULL);
if(newfd <0){
perror("accept");
exit(1);
}
//5. 读写
int ret = -1;
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
do{
ret = read(newfd,buf,BUFSIZ-1);
}while(ret <0 && EINTR == errno);//EINTR(中断),只有在ret < 0 并且读中断的时候,重新读,其他情况跳出循环
if(ret <0 ){
perror("read");
exit(1);
}
if(!ret){//ret > 0 ,对方已关网
break;
}
printf("Receive data: %s\n",buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)) ){//客户端输入"quit"退出。QUIT_STR == quit
printf("Client is exiting!\n");
break;
}
}
close(newfd);
close(fd);
//。。。FIXME!!!
return 0;
}
#include "net.h"
int main(int argc, char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
//1.创建socket fd
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
//2.连接服务器
//2.1填充struct sockaddr_in结构体变量
bzero(&sin,sizeof(sin));//置零结构体变量
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 0
sin.sin_addr.s_addrs = inet_addr(SERV_IP_ADDR);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void*)&sin.sin_addr.s_addr) != 1){
perror("inet_pton");
exit(1);
}
#endif
//2.2连接
if( connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("connect");
exit(1);
}
//3. 写
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin)==NULL){
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("Client is exiting!\n");
break;
}
}
//4.关闭连接
close(fd);
//。。。FIXME!!!
return 0;
}
并发服务器可以同时向所有发起请求的服务器提供服务,大大降低了客户端整体等待服务器传输信息的时间,同时,由于网络程序中,数据通信时间比CPU运算时间长,采用并发服务器可以大大提高CPU的利用率。
实现并发服务器有三种方法:
多进程服务器(通过创建多个进程提供服务)
多路复用服务器(通过捆绑并统一管理I/O对象提供服务)
多线程服务器(通过生成与客户端等量的线程提供服务)
头文件
#ifndef _MAKEU_NET_H_
#define _MAKEU_NET_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.3.63"
#define BACKLOG 5
#define QUIT_STR "quit"
#endif
服务端
#include
#include "net.h"
void cli_data_handle(void *arg);
int main(int argc, char *argv[])
{
int fd = -1;
struct sockaddr_in sin;
//1.创建socket fd
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
//优化4:允许绑定地址快速重用
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
//2.绑定
//2.1填充struct sockaddr_in结构体变量
bzero(&sin,sizeof(sin));//置零结构体变量
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 1
sin.sin_addr.s_addr =htonl(INADDR_ANY);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void*)&sin.sin_addr.s_addr) != 1){
perror("inet_pton");
exit(1);
}
#endif
//2.2绑定
if( bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("bind");
exit(1);
}
//3.调用listen()把主动套接字变成被动套接字
if(listen(fd,BACKLOG)<0){
perror("listen");
exit(1);
}
int newfd = -1;
//4.阻塞等待客户端连接请求
//用多进程/多线程处理已经建立好连接的客户端数据
pthread_t tid;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
if((newfd = accept(fd,(struct sockaddr*)&cin,&addrlen))<0){
perror("accept");
exit(1);
}
char ipv4_addr[16];
if(!inet_ntop(AF_INET,(void*)&cin.sin_addr,ipv4_addr,sizeof(cin))){
perror("inet_ntop");
exit(1);
}
printf("Clinet(%s:%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port));
pthread_create(&tid,NULL,(void*)cli_data_handle,(void*)&newfd);
}
close(fd);
return 0;
}
void cli_data_handle(void *arg){
int newfd = *(int*)arg;
printf("handler thread:newfd = %d\n",newfd);
//读写
int ret = -1;
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
do{
ret = read(newfd,buf,BUFSIZ-1);
}while(ret <0 && EINTR == errno);
if(ret <0 ){
perror("read");
exit(1);
}
if(!ret){//对方已关网
break;
}
printf("Receive data: %s\n",buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)) ){
printf("Client(fd=%d) is exiting!\n",newfd);
break;
}
}
close(newfd);
}
客户端
#include "net.h"
void usage (char *s)
{
printf ("\n%s serv_ip serv_port",s);
printf ("\n\t serv_ip: server ip address");
printf ("\n\t serv_port: server port(>5000)\n\n");
}
int main(int argc, char **argv)
{
int fd = -1;
int port = -1;
struct sockaddr_in sin;
if(argc != 3){
usage(argv[0]);
exit(1);
}
//1.创建socket fd
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
port =atoi(argv[2]);
if(port < 5000){
usage(argv[0]);
exit(1);
}
//2.连接服务器
//2.1填充struct sockaddr_in结构体变量
bzero(&sin,sizeof(sin));//置零结构体变量
sin.sin_family = AF_INET;
sin.sin_port = htons(port);//网络字节序的端口号
#if 0
sin.sin_addr.s_addrs = inet_addr(SERV_IP_ADDR);
#else
if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr) != 1){
perror("inet_pton");
exit(1);
}
#endif
//2.2连接
if( connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("connect");
exit(1);
}
//3. 写
char buf[BUFSIZ];
int ret = -1;
while(1){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin)==NULL){
continue;
}
do{
ret = write(fd,buf,strlen(buf));
}while(ret <0 && EINTR == errno);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("Client is exiting!\n");
break;
}
}
//4.关闭连接
close(fd);
//。。。FIXME!!!
return 0;
}
Makefile
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/long_term/io -p
cp $(PROGS) $(TARGET)/root/long_term/io -f
endif
% : %.c
$(CC) $(CFLAGS) $< -o $@ -lpthread
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/long_term/io && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../../farsight_network_1st_v1.1_for_1507.tar.gz ../../networks
ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
这两个函数一般在使用UDP协议时使用
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.7.246"
#define QUIT_STR "quit"
#endif
#include "net.h"
int main(void)
{
int fd = -1;
struct sockaddr_in sin;
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp程序
perror ("socket");
exit (1);
}
/* 2. 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/* 让服务器程序能绑定在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
char buf[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
printf("\nUDP server started!\n");
while(1) {
bzero(buf, BUFSIZ);
if( recvfrom(fd, buf, BUFSIZ-1, 0,(struct sockaddr *)&cin, &addrlen ) < 0) {
perror("recvfrom");
continue;
}
char ipv4_addr[16];
if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
perror ("inet_ntop");
exit (1);
}
printf("Recived from(%s:%d), data:%s",ipv4_addr, ntohs(cin.sin_port), buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs(cin.sin_port));
}
}
close(fd);
return 0;
}
/*udp demo */
/* usage:
* ./client serv_ip serv_port
*/
#include "net.h"
void usage(char *s)
{
printf("\nThis is udp demo!\n");
printf("\nUsage:\n\t %s serv_ip serv_port",s);
printf("\n\t serv_ip: udp server ip address");
printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}
int main(int argc, char *argv[])
{
int fd = -1;
int port = SERV_PORT;
port = atoi(argv[2]);
if(port < 0 || (port >0 && port <= 5000)) {
usage(argv[0]);
exit(1);
}
struct sockaddr_in sin;
if(argc !=3) {
usage(argv[0]);
exit(1);
}
/* 1. 创建socket fd*/
if( (fd = socket(AF_INET,SOCK_DGRAM, 0)) < 0) { //UDP编程
perror("socket");
exit(1);
}
/*2.1 填充struct sockaddr_in结构体变量 */
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr(argv[1]);
#else
if( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr) != 1) {
perror("inet_pton");
exit(1);
}
#endif
printf("UDP client started!\n");
char buf[BUFSIZ];
while(1) {
fprintf(stderr,"pls input string:");
bzero(buf, BUFSIZ);
if( fgets(buf, BUFSIZ-1, stdin) ==NULL) {
perror("fgets");
continue;
}
sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) { //用户输入了quit字符
printf("Client is exited!\n");
break;
}
}
close(fd);
return 0;
}
Makefile
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/long_term/io -p
cp $(PROGS) $(TARGET)/root/long_term/io -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/long_term/io && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../../farsight_network_1st_v1.1_for_1507.tar.gz ../../networks
在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:最常用
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:允许同时对多个I/O进行控制
信号驱动I/O:一种异步通信模型
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。
前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect
以read函数为例:
进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。
它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。
经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。
如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。这时,写操作不进行任何拷贝工作,将发生阻塞。
一量发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。
UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
这种模式使用中不普遍。
fcntl()函数
当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞。
代码实现;
int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);
多路复用:
基本常识:linux中每个进程默认情况下,最多可以打开1024个文件,最多有1024个文件描述符
文件描述符的特点:
1.非负整数
2.从最小可用的数字来分配
3.每个进程启动时默认打开0, 1,2三个文件描述符
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
select()
poll()
epoll()
SELECT基本原理:select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
POLL基本原理:poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
EPOLL基本原理:epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
#include
#include
#include
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
maxfd 所有监控的文件描述符中最大的那一个加1
read_fds 所有要读的文件文件描述符的集合
write_fds 所有要的写文件文件描述符的集合
except_fds 其他要向我们通知的文件描述符
timeout 超时设置
Null:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回。
在我们调用select时进程会一直阻塞直到以下的一种情况发生.
有文件可以读.
有文件可以写.
超时所设置的时间到.
//为了设置文件描述符我们要使用几个宏,宏的形式:
void FD_ZERO(fd_set *fdset) //从fdset中清除所有的文件描述符
void FD_SET(int fd,fd_set *fdset) //将fd加入到fdset
void FD_CLR(int fd,fd_set *fdset) //将fd从fdset里面清除
int FD_ISSET(int fd,fd_set *fdset) // 判断fd是否在fdset集合中,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作
select( )函数里面的各个文件描述符fd_set集合的参数在select( )前后发生了变化:
前:表示关心的文件描述符集合
后:有数据的集合(如不是在超时还回情况下)
那么究竟是谁动了fd_set集合的奶酪?
答曰:kernel
思考:
这种模式下,多路网络连接时候能否真正多路并发处理?如果能,请说明理由,如不能,请给出改进意见
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
//#include
#include
#define SERV_PORT 5002
#define SERV_IP_ADDR "192.168.7.246"
#define BACKLOG 5
#define QUIT_STR "quit"
#define SERV_RESP_STR "SERVER:"
typedef int data_t;
typedef struct node{
data_t data;
struct node *next;
}listnode,*linklist;
linklist list_create();
int list_tail_insert(linklist H, data_t value);//head
linklist list_get(linklist H, int pos);
int list_insert(linklist H, data_t value, int pos);
int list_delete(linklist H, linklist P);
int list_show(linklist H);
linklist list_free(linklist H);
#endif
int main (void)
{
fd_set rset;
int maxfd = -1;
struct timeval tout;
fd = socket ( ...);
bind (fd, ...);
listen (fd, ...);
while (1) {
maxfd = fd;
FD_ZERO (&rset);
FD_SET (fd, &rset);
//依次把已经建立好连接fd加入到集合中,记录下来最大的文件描述符maxfd
//...FIXME!!
#if 0
select (maxfd + 1, &rset, NULL, NULL, NULL);
#else
tout.tv_sec = 5;
tout.tv_usec = 0;
select (maxfd + 1, &rset, NULL, NULL, &tout);
#endif
if (FD_ISSET (fd, &rset)) {
newfd = accept (fd, ....);
}
//依次判断已建立连接的客户端是否有数据
//...FIXME!
}
return 0;
}
//select_model tcp
#include "net.h"
void *cli_data_handle(void* arg);
int main(void)
{
fd_set rset, rtmpset;//读集合,临时读集合
ssize_t recv_bytes , send_bytes;
int pzy = 1;
int i = 0;
int maxfd = -1;
int fd = -1;
int newfd = -1;
int ret = -1;
char buf[BUFSIZ];
char resp_buf[BUFSIZ+10];
struct timeval time1,time2;//原始时间,临时时间
//TCP协议,创建套接字,第二个参数为流式套接字类型
if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");//socket出错了
exit(1);//退出程序
}
//允许绑定地址快速重用
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &pzy, sizeof(int));
//服务器地址结构体填充,地址族,IP PORT
struct sockaddr_in ser,cli;//服务器结构体,客户端结构体
bzero(&ser, sizeof(ser));//对结构体ser清零
ser.sin_family = AF_INET;
ser.sin_port = htons(SERV_PORT);//本地字节序端口号变网络字节序端口号
#if 1
ser.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY是宏,明确值是1
#else
//本地字节序转网络字节序
//if((inet_pton(AF_INET,SERV_IP_ADDR,(void*)&sin.sin_addr.s_addr)) !=1) {
if((inet_pton(AF_INET,SERV_IP_ADDR,(void*)&ser.sin_addr)) != 1) {
perror("inet_pton failed");
exit(1);
}
#endif
//2.bind绑定套接字,服务器IP和端口号
if(bind(fd, (struct sockaddr*)&ser, sizeof(ser)) < 0) {
perror("bind failed");
exit(1);
}
// 将套接字设定为被动监听状态,监听客户端的连接请求,BACKLOG为未决队列长度
if(listen(fd, BACKLOG) < 0) {
perror("listen failed");
exit(1);
}
printf("Server is starting .....OK\n");
// maxfd = fd;//将监听套接字赋值给最大套接字
FD_ZERO(&rset);//对读集合清零
FD_ZERO(&rtmpset);//对临时读集合清零
FD_SET(fd, &rset);//把监听套接字fd加到读集合中
maxfd = fd;//
//依次把已经建立好连接fd加入到集合中,记录下来最大的文件描述符maxfd
#if 0
select(maxfd+1, &rset, NULL, NULL, NULL);
#else
//socklen_t len = sizeof(ser);//服务器的地址长度
socklen_t addrlen = sizeof(cli);//客户端的地址长度
time1.tv_sec = 5;//定时5秒
time1.tv_usec = 0;
while(1) {
rtmpset = rset;//将原始读集合赋值给备份集合
time2 = time1;//将原始时间赋值给临时时间
if(select(maxfd+1, &rtmpset, NULL, NULL, &time2) < 0) {
perror("select failed");
exit(1);//退出程序
}
for(i = 0; i <= maxfd; i++) {
if(FD_ISSET(i, &rtmpset)) {//判断文件描述符是否就绪
if(i == fd) {//监听套接字就绪
if((newfd = accept(fd, (struct sockaddr*)&cli, &addrlen )) < 0) {
perror("accept failed");
return -1;
}
FD_SET(newfd, &rset);//将新产生的连接套接字加入到原始表单
maxfd = (newfd > maxfd)? newfd:maxfd;//更新最大的文件描述符
char ipv4_addr[16];//字符串最后一位以“\0”结束
if(!inet_ntop(AF_INET, (void*)&cli.sin_addr, ipv4_addr, sizeof(cli))) {
perror("inet_ntop failed");
exit(1);
}
printf("Client (%s:%d) has connected\n",ipv4_addr, ntohs(cli.sin_port));
}else {//连接套接字
while(1) {
bzero(buf, sizeof(buf));//对缓冲区清零
do {
recv_bytes = recv(i, buf, sizeof(buf), 0);//接收客户端信息存到buf中
}while(recv_bytes < 0 && EINTR == errno);
if(recv_bytes == -1) {
perror("recv");
exit(1);
}
if(recv_bytes == 0) {
printf("client shutdown\n");
close(i);//关闭相应的文件描述符
FD_CLR(i, &rset);//清除
return -1;
}
printf("Client %d %s\n", i, buf);
bzero(resp_buf, BUFSIZ+10);
strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
strcat(resp_buf, buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))) {//两个字符串相等,也就是用户输入了“quit”这个字符串
printf("Client is exiting!\n");//打印客户端正在退出
close(i);//关闭相应的文件描述符
FD_CLR(i, &rset);//清除
exit(1);
}
char buff[128];
if(fgets(buff, BUFSIZ+10,stdin) == NULL) {
perror("fgets");
}
send_bytes = send(i, buff,strlen(buff), 0);//发给客户端
printf("***%d**data:%s\n",send_bytes,buff);//查看发送了几个字符给客户端
}
}
}
}
}
#endif
close(fd);
return 0;
}
#include "net.h"
int main (void)
{
fd_set rset;
int maxfd = -1;
struct timeval tout;
int fd = -1;
struct sockaddr_in sin;
linklist L,P,T;
//创建一个链表用于存取文件描述符
L = list_create();
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket");
exit (1);
}
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/*优化1: 让服务器程序能绑定在任意的IP上 */
sin.sin_addr.s_addr = htonl (INADDR_ANY);
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
/*3. 调用listen()把主动套接字变成被动套接字 */
if (listen (fd, BACKLOG) < 0) {
perror ("listen");
exit (1);
}
printf ("Server starting....OK!\n");
int newfd = -1;
struct sockaddr_in cin;
socklen_t addrlen = sizeof (cin);
// 阻塞等待客户端连接请求
while(1){
FD_ZERO(&rset);
FD_SET(fd,&rset);
P=L->next;
//找出最大的句柄
while(P){
if(P->data>maxfd)
//依次把已经建立好连接fd加入到集合中,记录下来最大的文件描述符maxfd
maxfd =P ->data;
FD_SET(P->data,&rset);
P=P->next;
}
//展示句柄列表
puts("展示句柄列表");
list_show(L);
//设置超时时间
tout.tv_sec = 5;
tout.tv_usec = 0;
select (maxfd + 1, &rset, NULL, NULL, &tout);
P=L->next;
依次判断已建立连接的客户端是否有数据
//遍历已经存在的连接
while(P){
if(FD_ISSET(P->data, &rset)){
newfd = P->data;
//读数据
int ret = -1;
char buf[BUFSIZ];
char resp_buf[BUFSIZ+10];
bzero (buf, BUFSIZ);
do {
ret = read (newfd, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read");
exit (1);
}
if (!ret) {//对方已经关闭,关闭句柄连接,删除结点信息。
close(P->data);
list_delete(L,P);
break;
}
//输出客户端的消息请求
printf ("Receive data: %s\n", buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
//响应客户端的quit
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(fd=%d) is exiting!\n", newfd);
break;
}
bzero(resp_buf, BUFSIZ+10);
strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
strcat(resp_buf, buf);
do {
//响应客户端一个返回信息SERV_RESP_STR+客户端请求数据
ret = write(newfd, resp_buf, strlen(resp_buf));
}while(ret < 0 && EINTR == errno);
}
P=P->next;
}
//检查是否有新的连接进入,判断如果是listenfd对应的文件描述符发生了事件,新的客户端发起连接请求
if (FD_ISSET(fd, &rset)) {
//创建一个与s同类的新的套接口并返回句柄。
if ((newfd = accept (fd, (struct sockaddr *) &cin, &addrlen)) < 0) {
perror ("accept");
break;
}
//将新创建的套接字的句柄加入链表
list_tail_insert(L,newfd);
}
}
close (fd);
return 0;
}
linklist list_create() {
linklist H;
H = (linklist)malloc(sizeof(listnode));
if (H == NULL) {
printf("malloc failed\n");
return H;
}
H->data = 0;
H->next = NULL;
return H;
}
int list_tail_insert(linklist H, data_t value) {
linklist p;
linklist q;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
//1 new node p
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc failed\n");
return -1;
}
p->data = value;
p->next = NULL;
q = H;
while (q->next != NULL) {
q = q->next;
}
//3 insert
q->next = p;
return 0;
}
int list_delete(linklist H, linklist p) {
linklist pri;
//1
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
//2 loocate prior
pri=H;
while(pri){
if(pri->next == p){
pri->next = p->next;
free(p);
break;
}
pri=pri->next;
}
return 0;
}
int list_show(linklist H) {
linklist p;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
p = H;
while (p->next != NULL) {
printf("%d ", p->next->data);
p = p->next;
}
puts("");
return 0;
}
/*./client serv_ip serv_port */
#include "net.h"
void usage (char *s)
{
printf ("\n%s serv_ip serv_port", s);
printf ("\n\t serv_ip: server ip address");
printf ("\n\t serv_port: server port(>5000)\n\n");
}
int main (int argc, char **argv)
{
int fd = -1;
int port = -1;
struct sockaddr_in sin;
if (argc != 3) {
usage (argv[0]);
exit (1);
}
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket");
exit (1);
}
port = atoi (argv[2]);
if (port < 5000) {
usage (argv[0]);
exit (1);
}
/*2.连接服务器 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (port); //网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr (SERV_IP_ADDR);
#else
if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("connect");
exit (1);
}
printf ("Client staring...OK!\n");
int ret = -1;
fd_set rset;
int maxfd = -1;
struct timeval tout;
char buf[BUFSIZ];
while (1) {
FD_ZERO (&rset);
FD_SET (0, &rset);
FD_SET (fd, &rset);
maxfd = fd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select (maxfd + 1, &rset, NULL, NULL, &tout);
if (FD_ISSET (0, &rset)) { //标准键盘上有输入
//读取键盘输入,发送到网络套接字fd
bzero (buf, BUFSIZ);
do {
ret = read (0, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read");
continue;
}
if (!ret)
continue;
if (write (fd, buf, strlen (buf)) < 0) {
perror ("write() to socket");
continue;
}
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
}
if (FD_ISSET (fd, &rset)) { //服务器给发送过来了数据
//读取套接字数据,处理
bzero (buf, BUFSIZ);
do {
ret = read (fd, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read from socket");
continue;
}
if (!ret)
break; /* 服务器关闭 */
//There is a BUG,FIXME!!
printf ("server said: %s\n", buf);
if ((strlen(buf) > strlen(SERV_RESP_STR))
&& !strncasecmp (buf+strlen(SERV_RESP_STR), QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Sender Client is exiting!\n");
break;
}
}
}
/*4.关闭套接字 */
close (fd);
}
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/net -p
cp $(PROGS) $(TARGET)/root/net -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/net && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../makeru_2_2_video_select_model.tar.gz ../2_2_video_select_model
1.执行make
2.执行./server
3.同目录新开终端,执行./client 127.0.0.1 5002 ,输入test1.
4.同目录新开终端,执行./client 127.0.0.1 5002 ,输入test1.
5.关闭一个client终端
#include
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFLEN 128
int createSocket(short port){
int serverFd,clientFd;
int len,ret,rlen;
struct sockaddr_in serverAddr;
len = sizeof(serverAddr);
serverFd = socket(AF_INET,SOCK_STREAM,0);
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = 0; //inet_addr("192.168.3.120");
int reuse = 1;
setsockopt(serverFd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int));
ret = bind(serverFd,(struct sockaddr *)&serverAddr,len);
if(ret<0){
perror("Failed to bind");
return -1;
}
ret = listen(serverFd,10);
if(ret<0){
perror("Failed to bind");
return -1;
}
return serverFd;
}
int main(int argc,char ** argv){
int sockfd;
short port;
int addrlen;
struct sockaddr_in serverAddr,clientAddr;
addrlen = sizeof(serverAddr);
char buf[BUFLEN];
if(argc!=2){
printf("Usage:%s port\n",argv[0]);
return 0;
}
port = atoi(argv[1]);
sockfd = createSocket(port);
if(sockfd<0){
return -1;
}
int epfd;
int ret;
int rdlen;
int i;
int clientFd;
struct epoll_event event;
struct epoll_event events[20];
memset(events,0,20*sizeof(struct epoll_event));
event.events = EPOLLIN ;
event.data.fd = sockfd;
epfd = epoll_create(1);
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
while(1){
ret = epoll_wait(epfd, events,20, -1); //阻塞等待时间到来
printf("epoll_wait return=%d\n",ret);
for(i=0;i<ret;i++){ //轮训到达的事件
if(events[i].data.fd == sockfd){//如果是监听的文件描述符有事件到来,接收新连接
clientFd = accept(events[i].data.fd,(struct sockaddr *)&clientAddr, &addrlen);
printf("new client %s,port=%d \n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
event.events = EPOLLIN;
event.data.fd = clientFd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientFd, &event);
}else{ //否则,是连接的客户端事件,读取数据
rdlen = read(events[i].data.fd,buf,BUFLEN);
if(rdlen>0){
printf("read buf=%s\n",buf);
}else if (rdlen==0){//客户连接中断,删除epoll监听的客户端文件描述符
event.events = EPOLLIN;
event.data.fd = events[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
}
}
}
}
}
说下select和epoll的区别:
1、select在每次被调用的时候,都会将所有fd从用户态转换成内核态,而epoll只是在事件注册的时候拷贝一次fd而已。提高了效率。
2、对于select来说,在每次醒着的时候,都需要将整个fd遍历一遍,而对于epoll来说,只需要在current的时候挂一遍fd,然后设置一个回调函数,当设备准备完成时,就调用一个回调函数将对应的文件描述符返还给进程,所以在时间上要大大的提高于select。
3、select的文件描述符的上限默认是1024,但是epoll没有这个限制,可以远大于1024,因为它只和系统的内存大小有关,而不受限于一个定值
参考直播: http://www.makeru.com.cn/live/5413_1937.html(参考代码见课程资料)
1、写基于tcp模型的IO多路复用(select)程序,在服务器端采用select来实现客户端的多路并发
2、epoll的原理和优缺点:
原理:
在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)
优点:
WireShark是非常流行的网络封包分析工具,可以截取各种网络数据包,并显示数据包详细信息。常用于开发测试过程中各种问题定位。
Linux 安装:sudo apt-get install wireshark
启动:sudo wireshark
使用telnet测试TCP服务器端
使用lsof
使用tcpdump
使用netstat
使用sniffer
使用wireshark
Chariot
…
SmartBit—硬件
网络层
IP 头固定部分20 个字节
identification 以太网进行传输包最多是1500bit ,大了需要拆散
TTL 生存周期,每过一个路由器减一
Header Checksum CRC16 认证
1、版本号字段占 4 位,给出 IP 版本号。
2、首部长度字段占 4 位,给出 IP 数据报的首部长度。
3、区分服务字段占 8 位,在旧标准种称为服务类型(Type Of Service,TOS)字段,用来指示期
望获得哪种类型的服务。
4、数据报长度字段占 16 位,指出 IP 数据报的总字节数。
5、标识字段占 16 位,标识一个 IP 数据报。用于在 IP 数据报分片和重组过程中,标识属于同一原
IP 数据报。
6、标志位字段占 3 位,其结构如下:
最高保留位
DF 禁止分片标志:
DF=0,允许路由器将该 IP 数据分片。
DF=1,禁止路由器将该 IP 数据分片。
MF 更多分片标志:
MF=0,该数据报未被分片或是分片的最后一片。
MF=1,该数据报一定是一个分片,且不是最后一个。
7、片偏移字段占 13 位,表示一个 IP 数据报分片与原 IP 数据报数据的相对偏移量,即封装的数据
分片从原整个数据报的哪个字节开始的。以 8 字节为单位。
当该字段值为 0 时,且 MF=1,则表示这是一个 IP 分片,且是第一个分片。
8、生存时间(Time-To-Live,TTL)字段占 8 位,表示 IP 数据报在网络中可以通过的路由器数(或
跳步数)。
9、上层协议字段占 8 位,指示该 IP 数据报封装的是哪个上层协议。TCP:6; UDP:17。用于
实现 IP 的多路复用与多路分解。
1、源端口号、目的端口号字段分别占 16 位,标识发送该报文段的源端口和目的端口,用于多路复
用/分解来自或送到上层应用的数据。
2、序号字段、确认序号字段分别占 32 位。
序号字段:该段所封装的应用层数据的第一个字节的序号。
确认序号字段:是期望从对方接收数据的字节序号,即该序号对应的字节尚未收到。
3、首部长度字段占 4 位。指出 TCP 段的首部长度,以 4 字节为计算单位。该字段最大取值为 15,
即 TCP 最大首部长度为 60 字节。
4、保留字段占 6 位。保留为今后使用,目前值为 0。
5、URG、ACK、PSH、RST、SYN、FIN 各占 1 位。
紧急 URG=1,紧急指针字段有效,优先传送。
确认 ACK=1,确认序号字段有效;ACK=0 时,确认序号字段无效。
推送 PSH=1,尽快将报文段中的数据交付接收应用进程,不要等缓存满了再交付。
复位 RST=1,TCP 连接出现严重差错,释放连接,再重新建立 TCP 连接。
同步 SYN=1,该 TCP 报文段是一个建立新连接请求控制段或者同意建立新连接的确认段。
终止 FIN=1,TCP 报文段的发送端数据已经发送完毕,请求释放连接。
6、接收窗口字段占 16 位。向对方通告我方接收窗口的大小(单位为字节),用于实现 TCP 流量控
制。
7、校验和字段占 16 位。校验和字段范围和计算方法与 UDP 相同。TCP 协议号是 6。
8、紧急指针字段占 16 位。该字段只有 URG=1 时才有效。指出在本 TCP 报文段中紧急数据共有多
少个字节。
9、选项字段长度可变。最长为 40 字节。
10、填充字段,取值全为 0,目的是为了整个首部长度是 4 字节的整倍数。
实现可靠数据传输有 5 种措施
,依据这些措施设计的可靠数据传输协议中最具代表性的是停-等协议、滑动窗口协议
实现可靠数据传输的措施主要包括以下几种:
1) 差错检测:利用差错编码实现数据包传输过程中的比特差错检测(甚至纠正)。
2) 确认:接收方向发送方反馈接收状态。
3) 重传:发送方重新发送接收方没有正确接收的数据。
4) 序号:确保数据按序提交。
5) 计时器:解决数据丢失问题。
停-等协议的工作过程。
答案及解析:停-等协议的基本工作过程是:发送方发送经过差错编码和编号的报文段,等待接收方
的确认;接收方如果正确接收报文段,即差错检测无误且序号正确,则接收报文段,并向发送方发
送 ACK,否则丢弃报文段,并向发送方发送 NAK;发送方如果收到 ACK,则继续发送后续报文段,否
则重发刚刚发送的报文段。
滑动窗口协议的基本工作过程
1、分组连续编号;
2、以流水线方式依次发送分组;
3、接收方接收分组,按分组序
号向上有序提交;
4、通过确认向发送方通告正确
接收的分组序号;
5、发送方根据收到的 ACK 的序
号以及计时器的,重新发送或者
继续发送新分组。
滑动窗口协议根据采用的确认、计时、窗口大小等机制的不同,可以设计不同的滑动窗口。
回退 N 步(GBN)协议
选择重传(SR)协议
请解释TCP的可靠传输原理
为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。那么,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制
等机制实现可靠性传输的。
TCP 实现可靠传输的方式之一,是通过序列号与确认应答。在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。但在错综复杂的网络,并不一定能如上图那么顺利能正常的数据传输,万一数据在传输过程中丢失了呢?所以 TCP 针对数据包丢失的情况,会用重传机制解决。
请叙述在client/server模型中,TCP的三次握手和四次握手过程
(1)第一次握手:Client将标志位SYN (synchronous建立联机)置为1,随机产生一个值seq(Sequence number (顺序号码))=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK(acknowledgement 确认)都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
gethostname() 获得主机名
getpeername() 获得与套接口相连的远程协议地址
getsockname() 获得本地套接口协议地址
gethostbyname() 根据主机名取得主机信息
endhostent()
gethostbyaddr() 根据主机地址取得主机信息
getprotobyname() 根据协议名取得主机协议信息
getprotobynumber() 根据协议号取得主机协议信息
getservbyname() 根据服务名取得相关服务信息
getservbyport() 根据端口号取得相关服务信息
IPv4中使用gethostbyname()函数完成主机名到地址解析,这个函数仅仅支持IPv4,且不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间。IPv6中引入了getaddrinfo()的新API,它是协议无关的,既可用于IPv4也可用于IPv6。
#include "net.h"
...h
struct sockaddr_in sin;
struct hostent *hs = NULL;
if ((hs = gethostbyname (argv[1])) == NULL) {
herror ("gethostbyname error");
exit (1);
}
...
sin.sin_addr.s_addr = *(uint32_t *) hs->h_addr;
endhostent ();//此函数告诉系统您不再期望使用gethostent从hosts文件中读取条目。
hs = NULL;
...
/*./client serv_name serv_port */
#include
#include "net.h"
void usage (char *s)
{
printf ("\n%s serv_ip serv_port", s);
printf ("\n\t serv_name: server domain name or ip address");
printf ("\n\t serv_port: server port(>5000)\n\n");
}
int main (int argc, char **argv)
{
int fd = -1;
int port = -1;
struct sockaddr_in sin;
struct hostent *hs = NULL;
if (argc != 3) {
usage (argv[0]);
exit (1);
}
port = atoi (argv[2]);
if (port < 5000) {
usage (argv[0]);
exit (1);
}
if ((hs = gethostbyname (argv[1])) == NULL) {
herror ("gethostbyname error");
exit (1);
}
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
perror ("socket");
exit (1);
}
/*2.连接服务器 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (port); //网络字节序的端口号
#if 1
sin.sin_addr.s_addr = *(uint32_t *) hs->h_addr;
endhostent ();
hs = NULL;
#else
if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
if (connect (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("connect");
exit (1);
}
printf ("Client staring...OK!\n");
int ret = -1;
fd_set rset;
int maxfd = -1;
struct timeval tout;
char buf[BUFSIZ];
while (1) {
FD_ZERO (&rset);
FD_SET (0, &rset);
FD_SET (fd, &rset);
maxfd = fd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select (maxfd + 1, &rset, NULL, NULL, &tout);
if (FD_ISSET (0, &rset)) { //标准键盘上有输入
//读取键盘输入,发送到网络套接字fd
bzero (buf, BUFSIZ);
do {
ret = read (0, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read");
continue;
}
if (!ret)
continue;
if (write (fd, buf, strlen (buf)) < 0) {
perror ("write() to socket");
continue;
}
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
}
if (FD_ISSET (fd, &rset)) { //服务器给发送过来了数据
//读取套接字数据,处理
bzero (buf, BUFSIZ);
do {
ret = read (fd, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read from socket");
continue;
}
if (!ret)
break; /* 服务器关闭 */
//There is a BUG,FIXME!!
printf ("server said: %s\n", buf);
if ((strlen (buf) > strlen (SERV_RESP_STR))
&& !strncasecmp (buf + strlen (SERV_RESP_STR), QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Sender Client is exiting!\n");
break;
}
}
}
/*4.关闭套接字 */
close (fd);
}
getsockopt和setsockopt
#include /* See NOTES */
#include
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); //可以用来设置快速重用
level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.(应用层)
2)IPPROTO_TCP:TCP选项. (传输层)
3)IPPROTO_IP:IP选项. (网络层)
optname指定控制的方式(选项的名称),我们下面详细解释
optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换
举例:
struct timeval {
long tv_sec; /* seconds : 秒*/
long tv_usec; /* microseconds: 微妙 */
};
在网络通信中,很多操作会使得进程阻塞
TCP套接字中的recv/accept/connect
UDP套接字中的recvfrom
超时检测的必要性
避免进程在没有数据时无限制地阻塞
当设定的时间到时,进程从原操作返回继续运行
参考代码如下
//设置socket的属性 SO_RCVTIMEO
//参考代码如下
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)); // 设置接收超时
recv() / recvfrom() // 从socket读取数据
参考代码如下
struct fd_set rdfs;
struct timeval tv = {5 , 0}; // 设置5秒时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪
{
recv() / recvfrom() // 从socket读取数据
}
参考代码如下
void handler(int signo) { return; }
struct sigaction act;
sigaction(SIGALRM, NULL, &act);
act.sa_handler = handler;
act.sa_flags &= ~SA_RESTART; //清除掉SIGALRM信号的SA_RESTART
sigaction(SIGALRM, &act, NULL);
alarm(5);
if (recv(,,,) < 0) ……
方法一: 数据交互双方隔一段时间,一方发送一点数据到对方,对方给出特定的应答。如超过设定次数大小的时间内还是没有应答,这时候认为异常
方法2:改变套接字的属性来实现
//函数定义:
void setKeepAlive (int sockfd, int attr_on, socklen_t idle_time, socklen_t interval, socklen_t cnt)
{
setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &attr_on, sizeof (attr_on));
setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (const char *) &idle_time, sizeof (idle_time));
setsockopt (sockfd, SOL_TCP, TCP_KEEPINTVL, (const char *) &interval, sizeof (interval));
setsockopt (sockfd, SOL_TCP, TCP_KEEPCNT, (const char *) &cnt, sizeof (cnt));
}
//函数调用
int keepAlive = 1;//设定keepalive
int keepIdle = 5;//开始首次keepAlive探测前的TCP空闭时间
int keepInterval = 5;//两次keepAlive探测间的时间间隔
int keepCount = 3;//判定断开前的keepAlive探测次数
setKeepAlive (newfd, keepAlive , keepIdle, keepInterval, keepCount );
试总结如何在linux中动态检查到是否有网络以及网络中途的掉线/连接的检查?
提示:
1.应用层
心跳检测
2.内核中
网卡驱动中 2.6内核里面,使能1s的周期性检查定时器
网卡硬件或者我们通过GPIO,插拔网线时候产生中断,处理相应中断 //立即检测到
广播和组播一定是UDP数据包
用于局域网内部一个发送,所有人接收
前面介绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
只有用户数据报(使用UDP协议)套接字才能广播
广播地址
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址
发到该地址的数据包被所有的主机接收
255.255.255.255 全网广播,在所有网段中都代表广播地址
创建用户数据报套接字
缺省创建的套接字不允许广播数据包,需要设置属性
setsockopt可以设置套接字属性
接收方地址指定为广播地址
指定端口信息
发送数据包
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
头文件:
level : 选项级别(例如SOL_SOCKET)
optname : 选项名(例如SO_BROADCAST)
optval : 存放选项值的缓冲区的地址
optlen : 缓冲区长度
返回值:成功返回0 失败返回-1并设置errno
sockfd = socket(,);
……
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
……
sendto(;;;;;
创建用户数据报套接字
绑定本机IP地址和端口
绑定的端口必须和发送方指定的端口相同
等待接收数据
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/long_term/io -p
cp $(PROGS) $(TARGET)/root/long_term/io -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/long_term/io && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../makeru_boardcast_demo.tar.gz ../boardcast_demo
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#define SERV_PORT 5003
#define SERV_IP_ADDR "192.168.7.246"
#define QUIT_STR "quit"
#endif
#include "net.h"
int main (void)
{
int fd = -1;
struct sockaddr_in sin;
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp程序
perror ("socket");
exit (1);
}
/* 2. 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/* 让服务器程序能绑定在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
char buf[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof (cin);
printf ("\nBoardcast receiver started!\n");
while (1) {
bzero (buf, BUFSIZ);
if (recvfrom (fd, buf, BUFSIZ - 1, 0, (struct sockaddr *) &cin, &addrlen) < 0) {
perror ("recvfrom");
continue;
}
char ipv4_addr[16];
if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
perror ("inet_ntop");
exit (1);
}
printf ("Recived boardcast data:%s\n", buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Sender(%s:%d) is exiting!\n", ipv4_addr, ntohs (cin.sin_port));
}
}
close (fd);
return 0;
}
/*udp demo */
/* usage:
* ./client serv_ip serv_port
*/
#include "net.h"
void usage (char *s)
{
printf ("\nThis is udp demo!\n");
printf ("\nUsage:\n\t %s serv_ip serv_port", s);
printf ("\n\t serv_ip: udp server ip address");
printf ("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}
int main (int argc, char *argv[])
{
int fd = -1;
int port = SERV_PORT;
port = atoi (argv[2]);
if (port < 0 || (port > 0 && port <= 5000)) {
usage (argv[0]);
exit (1);
}
struct sockaddr_in sin;
if (argc != 3) {
usage (argv[0]);
exit (1);
}
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //UDP编程
perror ("socket");
exit (1);
}
/* 允许广播设置 */
int b_br = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &b_br, sizeof(int));
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr (argv[1]);
#else
if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
printf ("broadcast demo started!\n");
char buf[BUFSIZ];
while (1) {
fprintf (stderr, "pls input string:");
bzero (buf, BUFSIZ);
if (fgets (buf, BUFSIZ - 1, stdin) == NULL) {
perror ("fgets");
continue;
}
sendto (fd, buf, strlen (buf), 0, (struct sockaddr *) &sin, sizeof (sin));
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exited!\n");
break;
}
}
close (fd);
return 0;
}
单播方式只能发给一个接收方,组播是一个人发送,加入到多播组的人接受数据。
广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
组播的IP地址: 224.0.0.1~239.255.255.254(中间除掉广播)
组播必须基于UDP的编程方法
A类地址
第1字节为网络地址,其他3个字节为主机地址。第1字节的最高位固定为0
1.0.0.1 – 126.255.255.255
B类地址
第1字节和第2字节是网络地址,其他2个字节是主机地址。第1字节的前两位固定为10
128.0.0.1 – 191.255.255.255
C类地址
前3个字节是网络地址,最后1个字节是主机地址。第1字节的前3位固定为110
192.0.0.1 – 223.255.255.255
D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
创建用户数据报套接字
接收方地址指定为组播地址
指定端口信息
发送数据包
创建用户数据报套接字
加入多播组
绑定本机IP地址和端口
绑定的端口必须和发送方指定的端口相同
等待接收数据
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“235.10.10.3”);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/net -p
cp $(PROGS) $(TARGET)/root/net -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/net && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../makeru_multicast_demo.tar.gz ../multicast_demo
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#define SERV_PORT 5004
#define MULTICAST_IP "235.10.10.3"
#define QUIT_STR "quit"
#endif
#include "net.h"
int main (void)
{
int fd = -1;
struct sockaddr_in sin;
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //udp程序
perror ("socket");
exit (1);
}
/* 2. 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/*加入多播组*/
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));
/*2. 绑定 */
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
/* 让服务器程序能绑定在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl (INADDR_ANY);
#else
if (inet_pton (AF_INET, SERV_IP_ADDR, (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("bind");
exit (1);
}
char buf[BUFSIZ];
struct sockaddr_in cin;
socklen_t addrlen = sizeof (cin);
printf ("\nmulticast demo started!\n");
while (1) {
bzero (buf, BUFSIZ);
if (recvfrom (fd, buf, BUFSIZ - 1, 0, (struct sockaddr *) &cin, &addrlen) < 0) {
perror ("recvfrom");
continue;
}
char ipv4_addr[16];
if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) {
perror ("inet_ntop");
exit (1);
}
printf ("Recived from(%s:%d), data:%s", ipv4_addr, ntohs (cin.sin_port), buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs (cin.sin_port));
}
}
close (fd);
return 0;
}
/*udp demo */
/* usage:
* ./client serv_ip serv_port
*/
#include "net.h"
void usage (char *s)
{
printf ("\nThis is multicast demo!\n");
printf ("\nUsage:\n\t %s serv_ip serv_port", s);
printf ("\n\t serv_ip: udp server ip address(between 224~239 segment)");
printf ("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}
int main (int argc, char *argv[])
{
int fd = -1;
int port = SERV_PORT;
port = atoi (argv[2]);
if (port < 0 || (port > 0 && port <= 5000)) {
usage (argv[0]);
exit (1);
}
struct sockaddr_in sin;
if (argc != 3) {
usage (argv[0]);
exit (1);
}
/* 1. 创建socket fd */
if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { //UDP编程
perror ("socket");
exit (1);
}
/*2.1 填充struct sockaddr_in结构体变量 */
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr (argv[1]);
#else
if (inet_pton (AF_INET, argv[1], (void *) &sin.sin_addr) != 1) {
perror ("inet_pton");
exit (1);
}
#endif
printf ("multicast started!\n");
char buf[BUFSIZ];
while (1) {
fprintf (stderr, "pls input string:");
bzero (buf, BUFSIZ);
if (fgets (buf, BUFSIZ - 1, stdin) == NULL) {
perror ("fgets");
continue;
}
sendto (fd, buf, strlen (buf), 0, (struct sockaddr *) &sin, sizeof (sin));
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exited!\n");
break;
}
}
close (fd);
return 0;
}
socket同样可以用于本地进程间的通信
创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。只用于同一主机内部进程间通信的socket应使用的协议族
socket(AF_LOCAL, SOCK_STREAM, 0)
socket(AF_LOCAL, SOCK_DGRAM, 0)
分为流式套接字和用户数据报套接字
和其他进程间通信方式3相比使用方便、效率更高
常用于前后台进程通信
本地地址结构
struct sockaddr_un //
{
sa_family_t sun_family;
char sun_path[108]; // 套接字文件的路径
};
填充地址结构
struct sockaddr_un myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sun_family = AF_UNIX;
strcpy(myaddr.sun_path, “/tmp/mysocket”);
# Makefile
#
#CROSS_COMPILE = arm-linux-gnu-
CC = $(CROSS_COMPILE)gcc
ifdef CROSS_COMPILE
TARGET = /opt/filesystem
endif
#DEBUG = -g -O0 -Wall
DEBUG = -g -O2
CFLAGS += $(DEBUG)
PROGS = ${patsubst %.c, %, ${wildcard *.c}}
all : $(PROGS)
install: $(PROGS)
ifdef CROSS_COMPILE
mkdir $(TARGET)/root/net -p
cp $(PROGS) $(TARGET)/root/net -f
endif
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: uninstall clean dist
uninstall :
ifdef CROSS_COMPILE
cd $(TARGET)/root/net && rm -f $(PROGS)
endif
clean : uninstall
- rm -f $(PROGS) core *.gz
dist: clean
tar czf ../makeru_unix_domain_demo.tar.gz ../unix_domain
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
//#include
#include
#include
#define UNIX_DOMAIN_FILE "/tmp/my_domain_file.1"
#define BACKLOG 5
#define QUIT_STR "quit"
#define SERV_RESP_STR "SERVER:"
#endif
#include
#include
#include "net.h"
void cli_data_handle (void *arg);
void sig_child_handle(int signo)
{
if(SIGCHLD == signo) {
waitpid(-1, NULL, WNOHANG);
}
}
int main (void)
{
int fd = -1;
signal(SIGCHLD, sig_child_handle);
/* 1. 创建socket fd */
if ((fd = socket (AF_LOCAL, SOCK_STREAM, 0)) < 0) { //基于本地的TCP通信
perror ("socket");
exit (1);
}
/* 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
/* 2.1 填充sockaddr_un结构体变量 */
struct sockaddr_un sun;
bzero(&sun, sizeof(sun));
sun.sun_family = AF_LOCAL;
/* 如果UNIX_DOMAIN_FILE所指向的文件存在,则删除 */
if(!access(UNIX_DOMAIN_FILE, F_OK)) {
unlink(UNIX_DOMAIN_FILE);
}
strncpy(sun.sun_path, UNIX_DOMAIN_FILE, strlen( UNIX_DOMAIN_FILE));
/*2.2 绑定 */
if (bind (fd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
perror ("bind");
exit (1);
}
/*3. 调用listen()把主动套接字变成被动套接字 */
if (listen (fd, BACKLOG) < 0) {
perror ("listen");
exit (1);
}
printf ("Unix domain server starting....OK!\n");
int newfd = -1;
/*4. 阻塞等待客户端连接请求 */
while(1) {
pid_t pid = -1;
if ((newfd = accept (fd, NULL,NULL)) < 0) {
perror ("accept");
break;
}
/*创建一个子进程用于处理已建立连接的客户的交互数据*/
if((pid = fork()) < 0) {
perror("fork");
break;
}
if(0 == pid) { //子进程中
close(fd);
printf ("Clinet is connected!\n");
cli_data_handle(&newfd);
return 0;
} else { //实际上此处 pid >0, 父进程中
close(newfd);
}
}
close (fd);
return 0;
}
void cli_data_handle (void *arg)
{
int newfd = *(int *) arg;
printf ("Child handling process: newfd =%d\n", newfd);
//..和newfd进行数据读写
int ret = -1;
char buf[BUFSIZ];
char resp_buf[BUFSIZ+10];
while (1) {
bzero (buf, BUFSIZ);
do {
ret = read (newfd, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read");
exit (1);
}
if (!ret) { //对方已经关闭
break;
}
printf ("Receive data: %s\n", buf);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client(fd=%d) is exiting!\n", newfd);
break;
}
bzero(resp_buf, BUFSIZ+10);
strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
strcat(resp_buf, buf);
do {
ret = write(newfd, resp_buf, strlen(resp_buf));
}while(ret < 0 && EINTR == errno);
}
close (newfd);
}
/*./client unix_domain_file */
#include "net.h"
void usage (char *s)
{
printf ("\n%s unix_domain_file\n\n", s);
}
int main (int argc, char **argv)
{
int fd = -1;
int port = -1;
struct sockaddr_un sun;
if (argc != 2) {
usage (argv[0]);
exit (1);
}
/* 1. 创建socket fd */
if ((fd = socket (AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror ("socket");
exit (1);
}
/*2.连接服务器 */
/*2.1 填充struct sockaddr_un结构体变量 */
bzero (&sun, sizeof (sun));
sun.sun_family = AF_LOCAL;
/*确保UNIX_DOMAIN_FILE要先存在并且可写,不存在则退出 */
if( access(UNIX_DOMAIN_FILE, F_OK| W_OK) < 0){
exit(1);
}
strncpy(sun.sun_path, UNIX_DOMAIN_FILE, strlen( UNIX_DOMAIN_FILE));
if (connect (fd, (struct sockaddr *) &sun, sizeof (sun)) < 0) {
perror ("connect");
exit (1);
}
printf ("Unix domain Client staring...OK!\n");
int ret = -1;
fd_set rset;
int maxfd = -1;
struct timeval tout;
char buf[BUFSIZ];
while (1) {
FD_ZERO (&rset);
FD_SET (0, &rset);
FD_SET (fd, &rset);
maxfd = fd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select (maxfd + 1, &rset, NULL, NULL, &tout);
if (FD_ISSET (0, &rset)) { //标准键盘上有输入
//读取键盘输入,发送到网络套接字fd
bzero (buf, BUFSIZ);
do {
ret = read (0, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read");
continue;
}
if (!ret)
continue;
if (write (fd, buf, strlen (buf)) < 0) {
perror ("write() to socket");
continue;
}
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
}
if (FD_ISSET (fd, &rset)) { //服务器给发送过来了数据
//读取套接字数据,处理
bzero (buf, BUFSIZ);
do {
ret = read (fd, buf, BUFSIZ - 1);
} while (ret < 0 && EINTR == errno);
if (ret < 0) {
perror ("read from socket");
continue;
}
if (!ret)
break; /* 服务器关闭 */
//There is a BUG,FIXME!!
printf ("server said: %s\n", buf);
if ((strlen(buf) > strlen(SERV_RESP_STR))
&& !strncasecmp (buf+strlen(SERV_RESP_STR), QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Sender Client is exiting!\n");
break;
}
}
}
/*4.关闭套接字 */
close (fd);
}
计算机网络中的“透明”性,主要表现在网络体系结构中层次关系的设计与利用两个方面:
1)从设计者的角度看,
“透明”性主要指各层的服务功能相对独立,具体实现的技术细节要尽可能的封装于本层之内,提供简化、规范、统一的调用接口。(即下层为上层提供透明调用)
2)从使用者(用户)的角度看,“透明”性主要指上层调用下层服务时,不需要了解下层的技术细节(甚至不知道、或认为不存在),通过简化、规范、统一的调用接口实现对下层服务的调用。(即上层看下层是“透明”的)比如,数据链路层是通过“0比特插入/删除”方法来实现数据帧的透明传输的,上层无论给出何种比特序列,数据链路层都能以数据帧的方式传输。 ↩︎
HTTP 是 Web 应用的应用层协议。定义浏览器如何向 Web 服务器发送请求以及 Web 服务器如何向浏览器
进行响应。
目前主要使用的 HTTP/1.0 和 HTTP/1.1,尤其以 HTTP/1.1 为主流。
讲解:HTTP 作为 Web 应用的应用层协议,依据每次请求后是否需要重新建立一个连接,分为非持久
连接和持久连接的 HTTP。因为非持久连接需要每次重新建立连接的弊端,提出了并行连接和持久连接的优
化技术,但并行连接有并行 TCP 连接数的限制,故产生了持久连接的方式。又根据是否可以连续依次发送
请求,分为非流水方式持久连接和流水方式持久连接。非流水方式必须等待一个对象请求并响应后才可以
对下一个对象请求,流水方式可以连续依次发送请求,节省时间,故现在流行的 HTTP/1.1 默认使用流水方
式持久连接。 ↩︎
进程间通信:
1.进程间的数据共享:
管道、消息队列、共享内存、unix域套接字
易用性: 消息队列 > unix域套接字 >管道 > 共享内存(经常要和信号量一起用)
效率: 共享内存 > unix域套接字 >管道 > 消息队列
常用:共享内存、unix域套接字
2.异步通信:
信号
3.同步和互斥(做资源保护)
信号量 ↩︎