织梦CMS - 轻松建站从此开始!

罗索

UNIX下的Socket编程

jackyhwei 发布于 2011-06-14 16:11 点击:次 
Sockets最早是作为BSD规范提出来的,并已成为Unix操作系统下TCP/IP网络编程标准,但是,随着网络技术的不断进步,Sockets的应用范围已不再局限于Unix操作系统和TCP/IP网络,但是我这次主要介绍是基于Linux上的Socket编程,但所有示例程序都在Sco以及 Linux上编译通过,并且运行
TAG:

一,前言

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 )


在<linux/in.h>中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);


在<netdb.h>中有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集合中

.......................................等等函数.

三,示例程序

整个程序测试,运行都是在SCO上完成.
client.c为客户端程序.
server.c为服务端程序.
obj.c为一些函数.
在下一个帖子集中贴出. 



用到生产环境没问题,只要加上对报文的处理,不要说你不知道什么是报文好了,更多内容请参照无双等兄弟写的好帖子.
http://www.loveunix.net/index.php?showtopic=21123
对了,里面有UDP的介绍和例子,俺就不写了..
(秩名)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201106/14562.html]
本文出处:loveunix.net 作者:秩名
顶一下
(1)
50%
踩一下
(1)
50%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容