LV.6 网络编程

D1 网络基础上

1.1网络的历史和分层

Internet的历史

Internet-“冷战”的产物
1957年10月和11月,前苏联先后有两颗“Sputnik”卫星上天
1958年美国总统艾森豪威尔向美国国会提出建立DARPA (Defense Advanced Research Project Agency),即国防部高级研究计划署,简称ARPA
1968年6月DARPA提出“资源共享计算机网络” (Resource Sharing Computer Networks),目的在于让DARPA的所有电脑互连起来,这个网络就叫做ARPAnet,即“阿帕网”,是Interne的最早雏形

网络互联促成了TCP/IP协议的产生

早期的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模型相关的协议已经很少使用,但模型本身非常通用
OSI模型是一个理想化的模型,尚未有完整的实现

在这里插入图片描述

1.2网络各层协议解释

TCP/IP协议是Internet事实上的工业标准。
在这里插入图片描述

TCP/IP协议通信模型

在这里插入图片描述
各层典型的协议:

  1. 网络接口与物理层
    MAC地址: 48位全球唯一,网络设备的身份标识
    ARP/RARP:
    ARP: IP地址----->MAC地址
    RARP: MAC地址—>IP地址
    PPP协议: 拨号协议(GPRS/3G/4G)
  2. 网络层:
    IP地址
    IP: Internet protocol(分为IPV4和IPV6)
    ICMP: Internet控制管理协议,ping命令属于ICMP
    IGMP: Internet分组管理协议,广播、组播
  3. 传输层:
    TCP: (Transfer Control protocol,传输控制协议) 提供面向连接的,一对一的可靠数据传输的协议
    即数据无误、数据无丢失、数据无失序、数据无重复到达的通信
    UDP: (user Datagram Protocol, 用户数据报协议): 提供不可靠,无连接的尽力传输协议
    是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
    SCTP: 是可靠传输,是TCP的增强版,它能实现多主机、多链路的通信3456790
  4. 应用层:
    网页访问协议:HTTP2/HTTPS
     邮件发送接收协议: POP3(收)/SMTP(发) 、IMAP(可接收邮件的一部分)
    FTP,
    Telnet/SSH: 远程登录
    嵌入式相关:
    NTP: 网络时钟协议
    SNMP: 简单网络管理协议(实现对网络设备集中式管理)
    RTP/RTSP:用传输音视频的协议(安防监控)

在这里插入图片描述

在这里插入图片描述

1.3网络的封包和拆包

TCP/IP协议下的数据包

在这里插入图片描述
在这里插入图片描述

TCP/IP结构

在这里插入图片描述

TCP协议特点

TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)

适用情况:
适合于对传输质量要求较高,以及传输大量数据的通信。
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议

UDP协议的特点

UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

适用情况:
发送小尺寸数据(如对DNS服务器进行IP地址查询时)	
在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)	
适合于广播/组播式通信中。	
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议	
流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输

D2 网络基础下

TCP 编程预备知识

SOCKET

  • 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地址

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)

  • 不同的机器HBO是不一样的,这与CPU的设计有关
    Motorola 68K系列、ARM系列,HBO与NBO是一致的
    Intel X86系列,HBO与NBO不一致

字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端问题

如CPU访问的是字符串,则不存在大小端问题
大端(Big-Endian):字节的高位在内存中放在存储单元的起始位置
在这里插入图片描述

小端(Little-Endian):与大端相反
在这里插入图片描述
一般来说:

X86/ARM: 小端
powerpc/mips, ARM作为路由器时,大端模式

网络传输的时候采用大端模式

本地字节序、网络字节序
在这里插入图片描述

IP地址转换函数

inet_aton()/inet_addr()

#include <arpa/inet.h>
//将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的转换
inet_pton()/inet_ntop()

#include <arpe/inet.h>
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

D3 TCP编程

tcp网络编程流程

服务器端:server.c
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与服务器网络信息结构体绑定 bind( )
将套接字设置为被动监听状态 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( )
客户端:client.c
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
发送客户端的连接请求 connect( )
进行通信 send( )/recv( )

连接测试:nc

相关函数

网络编程常用函数

socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听端口
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字

socket()函数

在这里插入图片描述
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

bind()函数

在这里插入图片描述
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

listen

int listen (int sockfd, int backlog);
sockfd:监听连接的套接字
backlog
指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5, 测试得知,ARM最大为8。内核中服务器的套接字fd会维护2个链表:

  1. 正在三次握手的的客户端链表(数量=2*backlog+1)
  2. 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
    DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。
    返回值: 0 或 -1
    RETURN VALUE
    On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

完成listen()调用后,socket变成了监听socket(listening socket)

accept()

#include <sys/types.h>
#include <sys/socket.h>

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()

connect()函数和服务器的bind()函数写法类似,connect()是客户端使用的系统调用。

#include <sys/types.h>
#include <sys/socket.h>
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

recv() 接收网络数据

#include <sys/socket.h>
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

send() 发送网络数据

#include <sys/socket.h>
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

read()/write()

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()时最好统一

close() 套接字的关闭

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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#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;
}

D4 并发服务器

TCP/IP网络编程

并发服务器可以同时向所有发起请求的服务器提供服务,大大降低了客户端整体等待服务器传输信息的时间,同时,由于网络程序中,数据通信时间比CPU运算时间长,采用并发服务器可以大大提高CPU的利用率。

TCP编程API

在这里插入图片描述
实现并发服务器有三种方法:
多进程服务器(通过创建多个进程提供服务)
多路复用服务器(通过捆绑并统一管理I/O对象提供服务)
多线程服务器(通过生成与客户端等量的线程提供服务)
在这里插入图片描述

多线程代码

头文件

#ifndef _MAKEU_NET_H_
#define _MAKEU_NET_H_

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.3.63"
#define BACKLOG 5    

#define QUIT_STR "quit"

#endif

服务端

#include <pthread.h>
#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
	

D5 UDP编程

udp网络编程流程

服务器:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与服务器网络信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
进行通信 sendto( )/recvfrom( )

相关函数

sendto(),recvfrom()

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协议时使用

UDP编程API

在这里插入图片描述
在这里插入图片描述

代码

头文件

#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>                  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>                 /* 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
	

D6 IO多路复用

6.1 IO模型

在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:最常用
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:允许同时对多个I/O进行控制
信号驱动I/O:一种异步通信模型

6.2 阻塞I/O 模式

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。
前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect

读阻塞

以read函数为例:
进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。
它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。
经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。
如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。

写阻塞

在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。这时,写操作不进行任何拷贝工作,将发生阻塞。
一量发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。
UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。

6.3 非阻塞模式I/O

当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
这种模式使用中不普遍。
在这里插入图片描述

非阻塞模式的实现

fcntl()函数
当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞。

代码实现;

1.fcntl( )函数

int fcntl(int fd, int cmd, long arg);

  int flag;
  flag = fcntl(sockfd, F_GETFL, 0);
  flag |= O_NONBLOCK;
  fcntl(sockfd, F_SETFL, flag);
2.ioctl() 函数
   int b_on =1;
   ioctl(sock_fd, FIONBIO, &b_on);

6.4 多路复用I/O

多路复用:
基本常识: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便可以收到通知。

select()
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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函数返回后,某个描述符是否准备好,以便进行接下来的处理操作  
TCP多路复用

在这里插入图片描述
关键点:

  1. select( )函数里面的各个文件描述符fd_set集合的参数在select( )前后发生了变化:
    前:表示关心的文件描述符集合
    后:有数据的集合(如不是在超时还回情况下)

  2. 那么究竟是谁动了fd_set集合的奶酪?
    答曰:kernel

思考:
这种模式下,多路网络连接时候能否真正多路并发处理?如果能,请说明理由,如不能,请给出改进意见

代码(select)
net.h
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>			/* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>			/* superset of previous */

#include <sys/select.h>
#include <sys/time.h>
//#include <sys/types.h>
#include <unistd.h>



#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


server.c(frame)
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;
}

server.c(同学)
//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;
}

server.c(自己用队列实现)
#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.c

/*./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
# 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	


readme.txt

1.执行make
2.执行./server
3.同目录新开终端,执行./client 127.0.0.1 5002 ,输入test1.
4.同目录新开终端,执行./client 127.0.0.1 5002 ,输入test1.
5.关闭一个client终端

poll()
#include <sys/poll.h>
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
epoll()
service.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>

#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的魅力所在。)
优点:

  1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。
    IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。
    缺点:
    如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。

D7 TCP、IP协议原理

7.1 网络分析测试工具、封包、IP和TCP头

wireshark

WireShark是非常流行的网络封包分析工具,可以截取各种网络数据包,并显示数据包详细信息。常用于开发测试过程中各种问题定位。

Linux 安装:sudo apt-get install wireshark
启动:sudo wireshark
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

常用调试测试工具

使用telnet测试TCP服务器端
使用lsof
使用tcpdump
使用netstat
使用sniffer
使用wireshark
Chariot

SmartBit—硬件

TCP/IP协议网络封包格式

在这里插入图片描述

以太网头

物理层
在这里插入图片描述
在这里插入图片描述

IP头

网络层
在这里插入图片描述
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 的多路复用与多路分解。
TCP头

传输层
面向连接,可靠传输
发送的每个字节都有编号,
在这里插入图片描述
在这里插入图片描述

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)协议
UDP头

在这里插入图片描述

在这里插入图片描述

7.2 TCP握手过程

TCP三次/四次握手

在这里插入图片描述

作业

  1. 请解释TCP的可靠传输原理

    为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。那么,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。
    TCP 实现可靠传输的方式之一,是通过序列号与确认应答。在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。但在错综复杂的网络,并不一定能如上图那么顺利能正常的数据传输,万一数据在传输过程中丢失了呢?所以 TCP 针对数据包丢失的情况,会用重传机制解决。

  2. 请叙述在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状态,完成四次挥手。

D8 网络编程扩展上

8.1域名解析

在这里插入图片描述

网络信息检索函数

gethostname() 获得主机名
getpeername() 获得与套接口相连的远程协议地址
getsockname() 获得本地套接口协议地址
gethostbyname() 根据主机名取得主机信息
endhostent()

gethostbyaddr() 根据主机地址取得主机信息
getprotobyname() 根据协议名取得主机协议信息
getprotobynumber() 根据协议号取得主机协议信息
getservbyname() 根据服务名取得相关服务信息
getservbyport() 根据端口号取得相关服务信息

gethostbyname()

使用步骤:
  1. netdb.h
  2. struct hostent *hs = NULL;
  3. hs = gethostbyname (argv[1]))
  4. sin.sin_addr.s_addr = *(uint32_t *) hs->h_addr;
    endhostent ();
说明

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.c

/*./client serv_name serv_port */
#include <netdb.h>
#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 <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
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获得或者是设置套接字选项.根据选项名称的数据类型进行转换 
在这里插入图片描述
举例:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
其中timeval的定义如下:

 struct timeval {
            long tv_sec;                /* seconds : 秒*/
            long tv_usec;               /* microseconds: 微妙 */
 };

8.2 网络超时

在网络通信中,很多操作会使得进程阻塞

TCP套接字中的recv/accept/connect

UDP套接字中的recvfrom

超时检测的必要性
避免进程在没有数据时无限制地阻塞
当设定的时间到时,进程从原操作返回继续运行

网络超时检测

设置socket的属性 SO_RCVTIMEO

参考代码如下

//设置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读取数据
用select检测socket是否’ready’

参考代码如下

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读取数据
}
设置定时器(timer), 捕捉SIGALRM信号

参考代码如下

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,插拔网线时候产生中断,处理相应中断 //立即检测到

D9 网络编程扩展下

9.1 广播和组播

广播和组播一定是UDP数据包

广播

用于局域网内部一个发送,所有人接收

前面介绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
只有用户数据报(使用UDP协议)套接字才能广播
广播地址
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址
发到该地址的数据包被所有的主机接收
255.255.255.255 全网广播,在所有网段中都代表广播地址
在这里插入图片描述

广播发送

创建用户数据报套接字
缺省创建的套接字不允许广播数据包,需要设置属性
setsockopt可以设置套接字属性
接收方地址指定为广播地址
指定端口信息
发送数据包

setsockopt

int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
头文件:<sys/socket.h>
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
# 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
	
net.h
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>			/* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>			/* superset of previous */

#define SERV_PORT 5003
#define SERV_IP_ADDR "192.168.7.246"

#define QUIT_STR "quit"

#endif
receiver.c
#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;
}

sender.c

/*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
# 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	

net.h
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>			/* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>			/* superset of previous */

#define SERV_PORT 5004
#define MULTICAST_IP "235.10.10.3"

#define QUIT_STR "quit"

#endif

receiver.c
#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;
}

sender.c

/*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;
}

9.2 UNIX域套接字

socket同样可以用于本地进程间的通信

创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。只用于同一主机内部进程间通信的socket应使用的协议族
socket(AF_LOCAL, SOCK_STREAM, 0)
socket(AF_LOCAL, SOCK_DGRAM, 0)

分为流式套接字和用户数据报套接字
和其他进程间通信方式3相比使用方便、效率更高
常用于前后台进程通信

本地地址结构
struct sockaddr_un // <sys/un.h>
{
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”);

UNIX域(流式)套接字

服务器端

在这里插入图片描述

客户端

在这里插入图片描述

UNIX域(用户数据报)套接字

服务器端

在这里插入图片描述

客户端

在这里插入图片描述

代码

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/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

net.h
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>			/* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>			/* superset of previous */

#include <sys/select.h>
#include <sys/time.h>
//#include <sys/types.h>
#include <unistd.h>
#include <sys/un.h>

#define UNIX_DOMAIN_FILE "/tmp/my_domain_file.1"

#define BACKLOG 5


#define QUIT_STR "quit"
#define SERV_RESP_STR "SERVER:"
#endif

server.c
#include <pthread.h>
#include <signal.h>
#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.c

/*./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. 计算机网络中的“透明”性,主要表现在网络体系结构中层次关系的设计与利用两个方面:
    1)从设计者的角度看,
    “透明”性主要指各层的服务功能相对独立,具体实现的技术细节要尽可能的封装于本层之内,提供简化、规范、统一的调用接口。(即下层为上层提供透明调用)
    2)从使用者(用户)的角度看,“透明”性主要指上层调用下层服务时,不需要了解下层的技术细节(甚至不知道、或认为不存在),通过简化、规范、统一的调用接口实现对下层服务的调用。(即上层看下层是“透明”的)比如,数据链路层是通过“0比特插入/删除”方法来实现数据帧的透明传输的,上层无论给出何种比特序列,数据链路层都能以数据帧的方式传输。 ↩︎

  2. HTTP 是 Web 应用的应用层协议。定义浏览器如何向 Web 服务器发送请求以及 Web 服务器如何向浏览器
    进行响应。
    目前主要使用的 HTTP/1.0 和 HTTP/1.1,尤其以 HTTP/1.1 为主流。
    在这里插入图片描述在这里插入图片描述
    讲解:HTTP 作为 Web 应用的应用层协议,依据每次请求后是否需要重新建立一个连接,分为非持久
    连接和持久连接的 HTTP。因为非持久连接需要每次重新建立连接的弊端,提出了并行连接和持久连接的优
    化技术,但并行连接有并行 TCP 连接数的限制,故产生了持久连接的方式。又根据是否可以连续依次发送
    请求,分为非流水方式持久连接和流水方式持久连接。非流水方式必须等待一个对象请求并响应后才可以
    对下一个对象请求,流水方式可以连续依次发送请求,节省时间,故现在流行的 HTTP/1.1 默认使用流水方
    式持久连接。 ↩︎

  3. 进程间通信:
    1.进程间的数据共享:
    管道、消息队列、共享内存、unix域套接字
    易用性: 消息队列 > unix域套接字 >管道 > 共享内存(经常要和信号量一起用)
    效率: 共享内存 > unix域套接字 >管道 > 消息队列
    常用:共享内存、unix域套接字
    2.异步通信:
    信号
    3.同步和互斥(做资源保护)
    信号量 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值