Ch5 Linux Network Programming Jianjian SONG Software Institute, Nanjing University Nov. 2004
Content 1.TCP/IP Review 2.Berkeley Socket Basics 3.System Calls and Routines 4.Network information 5.Multi-client 6.Using inetd/xinetd 7.Remote Procedure Call (RPC)
1. TCP/IP Review 互联网的历史 1969 年, ARPA 进行互联网试验。 1972 年, ARPAnet ,采用分组交换技术,使用 NCP 协议( Network Control Program ) 1973 年, TCP 的思想提出 1980 年, TCP/IP 加入 UNIX 内核 (BSD4.1) 1982 年, TCP/IP 取代 NCP 成 ARPAnet 标准 1983 年,开发出域名服务系统 (DNS) 1991 年, WWW 的出现 ……
TCP/IP & OSI Model
2. Berkeley Socket Basics TCP/IP in UNIX BSD4.1 socket connection: socket: socket 也是文件
要解决的问题 支持多种协议族 面向连接的服务和无连接的服务 地址的表示 ( 数据结构 ) 主机字节顺序和网络字节顺序
Three Types of Sockets 流套接字 (SOCK_STREAM) 可靠的、面向连接的通信。 使用 TCP 协议 数据报套接字 (SOCK_DGRAM) 无连接服务 使用 UDP 协议 原始套接字 (SOCK_RAW) 允许对底层协议如 IP 、 ICMP 直接访问
基于连接的服务
Server 程序的作用 程序初始化 持续监听一个固定的端口 收到 Client 的连接后建立一个 socket 连接 与 Client 进行通信和信息处理 接收 Client 通过 socket 连接发送来的数据,进行相 应处理并返回处理结果,如 BBS Server 通过 socket 连接向 Client 发送信息, 如 Time Server 通信结束后中断与 Client 的连接
Client 程序的作用 程序初始化 连接到某个 Server 上,建立 socket 连接 与 Server 进行通信和信息处理 接收 Server 通过 socket 连接发送来的数据,进行相 应处理 通过 socket 连接向 Server 发送请求信息 通信结束后中断与 Client 的连接
无连接的服务
UDP 编程的适用范围 部分满足以下几点要求时,应该用 UDP 面向数据报 网络数据大多为短消息 拥有大量 Client 对数据安全性无特殊要求 网络负担非常重,但对响应速度要求高 例子: ICQ 、视频点播
具体编程时的区别 socket() 的参数不同 UDP Server 不需要调用 listen 和 accept UDP 收发数据用 sendto/recvfrom 函数 TCP :地址信息在 connect/accept 时确定 UDP : 在 sendto/recvfrom 函数中每次均需指定地址 信息 UDP : shutdown 函数无效
Byte order 网络字节顺序 ( NBO , Network Byte Order ) 使用统一的字节顺序,避免兼容性问题 主机字节顺序 ( HBO , Host Byte Order ) 不同的机器 HBO 不相同,与 CPU 设计有关 Motorola 68k 系列, HBO 与 NBO 相同 Intel x86 系列, HBO 与 NBO 相反
Byte Ordering Routines Convert values between host and network byte order #include uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t hostlong); uint16_t ntohs(uint16_t hostshort);
3. System Calls & Routines
socket system call header file #include socket: creates an endpoint for communication and returns a descriptor. int socket(int domain, int type, int protocol);
socket system call (cont ’ d) “ domain ” parameter Specifies a communication domain, that is, selects a protocol family, such as PF_UNIX(PF_LOCAL), PF_INET, PF_IPX... “ type ” parameter Specifies the communication semantics. Three main types are: SOCK_STREAM, SOCK_DGRAM, SOCK_RAW “ protocol ” parameter usually 0 (default).
bind system call bind: binds a name to a socket int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); struct sockaddr ? struct sockaddr { sa_family_t sa_family; char sa_data[14]; } ->struct sockaddr_un ->struct sockaddr_in
bind system call (cont ’ d) struct sockaddr_un struct sockaddr_un{ sa_family_t sun_family; /* AF_UNIX */ char sun_path[108]; /*pathname */ };
bind system call (cont ’ d) struct sockaddr_in struct sockaddr_in{ sa_family_t sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }; struct in_addr{ __u32 s_addr; };
inet_aton & inet_ntoa Internet address manipulation routines int inet_aton(const char *cp, struct in_addr *inp); char* inet_ntoa (struct in_addr in); inet_ntoa 将一个 32 位数字表示的 IP 地址转换成点分 十进制 IP 地址字符串 inet_addr: An obsolete interface to inet_aton inet_addr_t inet_addr (const char *cp);
const INADDR_ANY An unsigned long int value. When INADDR_ANY is specified in the bind call the socket will be bound to all local interfaces.
listen system call listen: listen for connections on a socket int listen(int s, int backlog); 被动倾听的 socket 需要在此前调用 bind() 函数,否则由系统指定一个 随机的端口 连接队列 一个新的 Client 的连接请求先被放在连接队列中, 等待 Server 程序调用 accept 函数接受连接请求 backlog 指的就是接收队列的长度,亦即 Server 程序 调用 accept 函数之前最大允许的连接请求数,多余 的连接请求将被拒绝
accept system call accept() 函数将响应连接请求,建立连接 int accept(int sockfd,struct sockaddr *addr,int *addrlen); accept 缺省是阻塞函数,阻塞直到有连接请求 sockfd: 被动 ( 倾听 ) 的 socket 描述符 如果成功,返回一个新的 socket 描述符 (connected socket descriptor) 来描述该连接。这个连接用来与 特定的 Client 交换信息 addr 将在函数调用后被填入连接对方的地址信息, 如对方的 IP 、端口等。
connect system call connect: initiate a connection on a socket (connect to a server). int connect(int sockfd, struct sockaddr *servaddr, int addrlen); 主动的 socket servaddr 是事先填写好的结构, Server 的 IP 和 端口都在该数据结构中指定。
send/recv send/recv: connection-oriented int send(int s, const void *msg, size_t len, int flag); int recv(int s, void *buf, size_t len, int flag); 与 write/read 比较 ssize_t write(int fd, const void *buf, size_t count); ssize_t read(int fd, void *buf, size_t count); flag: send: MSG_OOB, MSG_DONTROUTE, MSG_DONTWAIT, MSG_NOSIGNAL,... recv: MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_NOSIGNAL,...
sendto/recvfrom int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socketlen_t tolen); int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
close & shutdown close int close(int sockfd); shutdown int shutdown(int sockfd, int how); how: SHUT_RD, SHUT_WR, SHUT_RDWR shutdown 直接对 TCP 连接进行操作, close 只是 对套接字描述符操作。
4. Examples 简单的流 socket C/S 应用 数据报 socket C/S 应用
5. Network information Host name and address conversions Service name and port number conversions
Host name and address conversions Domain Name System
Host name and address conversions (cont ’ d) Files: /etc/resolv.conf, /etc/hosts Functions #include struct hostent *gethostbyname(const char *name); struct hostent *gethostbyaddr(const char *addr, size_t len, int type); struct hostent struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addrlist; /* list of addresses */ };
Service name and port number conversions File: /etc/services Functions: #include struct servent *getservbyname(const char *name, const char *proto); struct servent *getservbyport(int port, const char *proto); struct servent struct servent { char *s_name; /* official service name */ char **s_aliases; /* alias list */ int s_port; /* port number */ char *s_proto; /* protocol to use */ };
Review Berkley Socket 常用函数 网络连接函数 获取 / 设置 socket 的参数或信息 转换函数
网络连接函数 socket bind connect listen accept recv, recvfrom send, sendto close, shutdown
获取 / 设置 socket 的参数或信息 gethostbyaddr, gethostbyname getservbyname, getservbyport getsockname, getpeername getsockopt, setsockopt fcntl/ioctl
转换函数 IP 地址转换 inet_aton() inet_ntoa() 字节顺序转换 htons()--"Host to Network Short" htonl()--"Host to Network Long" ntohs()--"Network to Host Short" ntohl()--"Network to Host Long"
6. Multi-client Multi-client, n servers? Iterative server Concurrent server Multi-process/multi-thread I/O Models Block mode Non-block mode I/O multiplexing Signal-driven I/O Asynchronous I/O
Multi-process server
I/O Models Block model default Non-block model I/O multiplexing Signal-driven I/O Asynchronous I/O
Non-block mode 阻塞与非阻塞方式的比较 errno - EWOULDBLOCK 非阻塞的实现 int flags; if ( (flags=fcntl(sock_fd, F_GETFL, 0)) < 0) err_sys(); flags |= O_NONBLOCK; if ( fcntl(sock_fd, F_SETFL, flags) < 0) err_sys();
Signal-driven I/O 用于接收紧急数据 带外数据 SIGURG, SIGIO 实现 fcntl(conn_fd, F_SETOWN, getpid()); sigaction
I/O multiplexing 基本思想: 先构造一张有关描述符的表,然后调用一个 函数 ( 如 select) ,该函数到这些描述符中的 一个已准备好进行 I/O 时才返回,返回时告 诉进程哪个描述符已准备好进行 I/O.
“ select ” select: synchronous I/O multiplexing. #include int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); FD_ZERO(fd_set *set); FD_SET(int fd, fd_set *set); FD_CLR(int fd, fd_set *set); FD_ISSET(int fd, fd_set *set);
7. Using inetd/xinetd The disadvantages of the old model inetd/xinetd: Internet service daemon (also referred as super-server). /etc/inetd.conf or /etc/xinetd.conf
xinetd 启动服务的过程 xinetd 启动时读取 /etc/xinetd 目录中的文件(早期版本 为 /etc/inetd 文件),根据其中的内容给所有允许启动 的服务创建一个指定类型的套接口,并将套接口放入 select() 中的描述符集合中。 对每个套接口绑定 bind() ,所用的端口号和其它参数来 自 /etc/xinetd 目录下每个服务的配置文件。 如果是 TCP 套接口就调用函数 listen() ,等待用户连接。 如果是 UDP 套接口,就不需调用此函数。 所有套接口建立后,调用函数 select() 检查哪些套接口 是准备好的。 若 select() 返回 TCP 套接口,就调用 accept() 接收这个连 接。如果为 UDP ,就不需调用此函数。
xinetd 启动服务的过程 ( 续 ) xinetd 调用 fork() 创建子进程,由子进程处理连接请求。 子进程关闭所有其它描述符,只剩下套接口描述符。这个套 接口描述符对于 TCP 是 accept() 返回的套接口,对于 UDP 为最 初建立的套接口。然后子进程连续三次 dup() 函数,将套接口 描述符复制到 0 、 1 和 2 ,它们分别对应标准输入、标准输出和 标准错误输出,并关闭套接口描述符。 子进程查看 /etc/xinetd 下文件中的用户,如果不是 root 用户, 就用调用命令 setuid 和 setgid 将用户 ID 和组 ID 改成文件中指定 的用户 。 对于 TCP 套接口,与用户交流结束后父进程需要关闭已 连接套接口。父进程重新处于 select() 状态,等待下一 个可读的套接口。 最后调用配置文件中指定的外部服务程序,外部程序 启动后就可与用户进行信息传递了。
Example 用 xinetd 实现网络服务 hello.c
8. RPC RPC Remote Procedure Call IPC vs. RPC Explicit vs. implicit network programming RPC->Distributed Object (CORBA, RMI, etc.)
Review