原文链接:http://www.host01.com/article/server/00070002/0621409103916080.htm
一,前言
TCP(Transfer Control Protocol)传输控制协议是一种面向连接的协议,客户端和服务端的连接是可靠的,安全的.
Sockets最早是作为BSD规范提出来的,并已成为Unix操作系统下TCP/IP网络编程标准,但是,随着网络技术的不断进步,
Sockets的应用范围已不再局限于Unix操作系统和TCP/IP网络,但是我这次主要介绍是基于Linux上的Socket编程,但所有
示例程序都在Sco以及Linux上编译通过,并且运行正常.
一般的网络程序分为server端和client端.
二,函数详细介绍
1, int socket(int domain, int type,int protocol)
domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等).
其实这里指定的是地址族,在unix中协议族和地址族是一一对应的.
AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,
因而可以允许在 远程 主机之间通信(当我们 man socket时发现 domain可选项
是 PF_*而不是AF_*,因为glibc是posix的实现 所以用PF代替了AF,实际上查看
sys/socket.h会发现如下定义:#define AF_INET 2 ,#define PF_INET AF_INET).
type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等)
SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.
SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
SOCK_RAW提供对internal network interfaces的访问,只有特权程序才能使用。对应IP协议、ICMP协议等等。
protocol:由于我们指定了type,所以对应了单一的protocal,所以这个地方我们一般只要用0来代替就可以了 .
如果存在多协议,则必须明确指定.
socket为网络通讯做基本的准备.成功时返回一个socket号,失败时返回-1.
server端和client端都使用本函数.
2, int bind(int sfd, struct sockaddr_in *addr, int len)
sfd:是由socket调用返回的描述符即socket号.
addr:是一个指向sockaddr_in的指针.
len:是sockaddr结构的长度. sizeof ( struct sockaddr )
在中sockaddr_in的定义为
struct sockaddr_in
{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
sin_family一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何的主机通信,
sin_port是我们要监听的端口号.
sin_zero[8]暂时未用
bind后将本地的端口同socket返回的描述符捆绑在一起.成功是返回0,失败返回-1.
属于server端函数.
3, int listen(int sfd,int qlen)
sfd:是server方的socket号.
qlen:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示
可以接收的排队长度.一般我们都写5. listen函数将bind的文件描述符变为监听套接字.
成功返回0,失败返回-1.本函数必须在accept之前调用.
属于server端函数.
4, int accept(int sfd, struct sockaddr_in *addr,int *len)
sfd:是server方的socket号.
addr,addrlen是用指针接收client端填写的信息. 调用前都做一个初始化.调用时,
server端的程序会一直阻塞直到到有一个client调用connect函数请求了连接. accept
成功时返回一个非负整数表示accepted socket,这时server端可以向该描述符写信息了.
失败时返回-1 .
在并发服务器方式中,server 端fork出一个从服务器,从服务器利用调用返回的accepted
socket与client通信。
在面向连接的通信中,client可以不调用bind,connect会自动完成。
在无连接通信中,客户方必须调用bind。connect主要是为面向连接通
信的客户设计的,accept则完全是为面向连接通信的服务器设计的。
connect可以用于无连接的服务器以及客户,其作用可以代替bind,
但比bind功能强大。
属于server端函数.
5, int connect(int sfd, struct sockaddr_in *serv_addr,int addrlen)
sfd:socke fd.
serveraddr:储存了server端的连接信息.描述了它的连接目的地.
addrlen:serveraddr的长度.
connect函数是client端连接server的.成功时返回0, 失败时返回-1.
6, IP和域名的转换的一组函数
struct hostent *gethostbyname(const char *hostname)
struct hostent *gethostbyaddr(const char *addr,int len,int type)
struct hostent *gethostbyname_r(const char *name, struct hostent *result,
char *buffer, int buflen, int *h_errnop);
struct hostent *gethostbyaddr_r(const char *addr, int length, int type,
struct hostent *result, char *buffer,
int buflen, int *h_errnop);
在中有struct hostent的定义
struct hostent
{
char *h_name; /* 主机的名称 */
char *h_aliases; /* 主机的别名 */
int h_addrtype; /* 主机的地址类型 AF_INET*/
int h_length; /* 主机的地址长度 对于IP4 是4字节32位*/
char **h_addr_list; /* 主机的IP地址列表 */
}
gethostbyname可以将机器名(如 Diablo )转换为一个结构指针.在这个结构里面储存了域名的信息
gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针.
gethostbyname()、gethostbyaddr()使用了静态数据区,这些静态数据区会在每次函数调用
中都使用到,在多线程应用中使用这些函数是有问题的。
失败时均返回NULL 且设置h_errno,调用h_strerror()可以得到详细错误信息.
gethostbyname_r()、gethostbyaddr_r()是支持重入的版本。参数result必须是一个指向
struct hostent的指针,该结构所用内存空间必须是调用者明确分配下来的。若成功,主
机描述信息将返回到这个结构中。参数buffer必须指向由调用者提供的缓冲空间。buffer
用于存放主机数据,返回值struct hostent 中所有的指针均指向存放在buffer中的数据。
buffer必须足够大以致能存放所有可能返回的数据。参数buflen给出buffer的字节大小。
参数h_errnop是一个整型指针,发生错误时这里存放了错误信息。
7, 字节转换函数
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
h 代表host, n 代表 network.s 代表short l 代表long
第一个函数的意义是将本机上的long型转化为网络上的long型.
第二个函数的意义是将本机上的short型转化为网络上的short型.
第三个函数的意义是将网络上的long型转化为本机上的long型.
第四个函数的意义是将网络上的short型转化为本机上的short型.
为什么会有这样几个函数呢?是因为在网络的机器在表示数据的字节顺序是不同的,为了统一起来,
有了专门的字节转换函数.
其实这些函数大多数是用作关联gethostbyname和getservent返回的地址和端口号的.
8, IP处理系列函数
char *inet_ntoa(struct in_addr in)
int inet_aton(const char *cp,struct in_addr *inp)
函数里面 a 代表 ASCII ,n 代表network.
第一个函数是将32位网络IP转换为a.b.c.d的ASCII格式.
第二个函数如果地址有效,会返回1,否则函数返回0.
9, 服务信息函数
int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
{
char *s_name; /* 正式服务名 */
char **s_aliases; /* 别名列表 */
int s_port; /* 端口号 */
char *s_proto; /* 使用的协议 */
}
一般我们很少用这几个函数.对应客户端,当我们要得到连接的端口号时在connect调用成
功后使用可得到 系统分配的端口号.对于服务端,我们用INADDR_ANY填充后,为了得到连
接的IP我们可以在accept调用成功后 使用而得到IP地址.
在网络上有许多的默认端口和服务,比如端口21对ftp80对应WWW.为了得到指定的端口号
的服务 我们可以调用第四个函数,相反为了得到端口号可以调用第三个函数.
10 , 读写函数
写函数
ssize_t write(int sfd,const void *buf,size_t nbytes)
write函数将buf中的nbytes字节内容写入描述符sfd.
成功时返回写的字节数.失败时返回-1. 并设置errno变量.
1)write的返回值大于0,表示写了部分或者是全部的数据.
2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.
如果错误为EINTR表示在写的时候出现了中断错误.
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
读函数
ssize_t read(int fd,void *buf,size_t nbyte)
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,
如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
如果错误为EINTR说明读是由中断引起的, 如果是ECONNREST表示网络
连接出了问题.
其实大多数时候我们都是传递一个报文,一般我们会把一个结构memcpy到一个
buffer中,然后将这个buffer str在网络上传递.
11 , UDP函数
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from ,int *fromlen)
int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to ,int tolen)
sockfd,buf,len的意义和read,write一样,分别表示套接字描述符,发送或接收的缓冲区及大小.
recvfrom负责从sockfd接收数据,如果from不是NULL,那么在from里面存储了信息来源的情况,
如果对信息的来源不感兴趣,可以将from和fromlen设置为NULL.sendto负责向to发送信息.
此时在to里面存储了收信息方的详细资料.
12 , 高级套接字函数 之recv和send
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)
recv和send函数提供了和read和write差不多的功能.不过它们提供了第四个参数来控制
读写操作.
前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合
MSG_DONTROUTE,不查找路由表
MSG_OOB,接受或者发送带外数据
MSG_PEEK,查看数据,并不从系统缓冲区移走数据
MSG_WAITALL,等待所有数据
MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面
,没有必要查找路由表.这个标志一般用网络诊断和路由程序里面.
MSG_OOB:是send函数使用的标志.表示可以接收和发送带外的数据.
MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲
区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用
这个标志.
MSG_WAITALL是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的
时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误.
1)当读到了指定的字节时,函数正常返回.返回值等于len
2)当读到了文件的结尾时,函数正常返回.返回值小于len
3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno)
如果flags为0,则和read,write一样的操作.上面介绍的是我们常用的几个参数,当然还有其它
的几个选项,不过我们实际上用的很少.
13 ,高级套接字函数 之recvmsg和sendmsg
recvmsg和sendmsg可以实现前面所有的读写函数的功能.
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}
struct iovec
{
void *iov_base; /* 缓冲区开始的地址 */
size_t iov_len; /* 缓冲区的长度 */
}
msg_name和 msg_namelen当套接字是非面向连接时(UDP),它们存储接收和发送方的地址
信息.msg_name实际上是一个指向struct sockaddr的指针,msg_namelen是结构的长度.
当套 接字是面向连接时,这两个值应设为NULL.
msg_iov和msg_iovlen指出接受和发送的缓冲区内容.msg_iov是一个结构指针,msg_iovlen
指出这个结构数组的大小.
msg_control和msg_controllen这两个变量是用来接收和发送控制数据时的 ??
msg_flags指定接受和发送的操作选项,和recv,send的选项一样.
14 ,高级套接字函数 之shutdown
int shutdown(int sockfd,int howto)
TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希
望只关闭一个方向,这个时候我们可以使用shutdown.针对不同的howto,系统会采取不同
的关闭方式.
howto=0这个时候系统会关闭读通道.但是可以继续往接字描述符写.
howto=1关闭写通道,和上面相反,这时候就只可以读了.
howto=2关闭读写通道,和close一样 .
在多进程程序里面,如果有几个子进程共享一个套接字时,如果我们使用shutdown, 那么所
有的子进程都不能够操作了,这个时候我们只能够使用close来关闭子进程的套接字描述符.
15 ,高级套接字函数 getsockopt和setsockopt
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_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
对应的optname详细说明
optname指定控制的方式(选项的名称).
选项名称 说明 数据类型
========================================================================
SOL_SOCKET
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
IPPROTO_IP
IP_HDRINCL 在数据包中包含IP首部 int
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
IPPRO_TCP
TCP_MAXSEG TCP最大数据段的大小 int
TCP_NODELAY 不使用Nagle算法 int
=========================================================================
optval获得或者是设置套接字选项.ON或者OFF等 .
16 ,高级套接字函数 ioctl
int ioctl(int fd,int req,...)
ioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项.
ioctl的控制选项
SIOCATMARK 是否到达带外标记 int
FIOASYNC 异步输入/输出标志 int
FIONREAD 缓冲区可读的字节数 int
17 , 高级套接字函数 select
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比
如我们从一个套接字读数据时,可能缓冲区里面没有数据可读(通信的对方还没有 发送数
据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞
,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件
可以读写的时候select回"通知"我们说可以读写了.
readfds所有要读的文件文件描述符的集合
writefds所有要的写文件文件描述符的集合
exceptfds其他的服要向我们通知的文件描述符
timeout超时设置.
nfds所有我们监控的文件描述符中最大的那一个加1
在我们调用select时进程会一直阻塞直到以下的一种情况发生.
1)有文件可以读.
2)有文件可以写.
3)超时所设置的时间到.
为了设置文件描述符我们要使用几个宏.
FD_SET将fd加入到fdset
FD_CLR将fd从fdset里面清除
FD_ZERO从fdset中清除所有的文件描述符
FD_ISSET判断fd是否在fdset集合中