在Linux操作系统中,要实现socket通信,通信双方都需要建立各自的socket对象,在应用层,socket对象是一种特殊的文件描述符,可以使用I/O系统调用(read/write)来读写。socket()函数用于创建socket,其函数声明如下:
//come from /user/include/sys/socket.h
/* Create a new socket of type TYPE in domain DOMAIN, using protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically. Returns a file descriptor for the new socket, or -1 for errors.*/
extern int socket (int __domain, int __type, int __protocol)
此函数如果执行成功,将返回一个打开的socket文件描述符,此时,该socket对象没有绑定任何IP信息,还不能进行通信,如果执行失败,将返回-1。
第1个参数用来指明此socket对象所使用的地址簇或协议簇,即此对象所使用的通信协议类型。(TCP/IP协议栈是当前网络应用的主流,但BSD socket仍然支持其他类型的协议栈,目前经常使用的有PF_LOCAL(本机协议)、PF_INET(IPv4协议簇)和PF_INET6(IPv6)等。)
第2个参数为socket的类型。在usr/include/bits/socket.h文件描述了可选用类型,具体如下所示:
//come from /usr/include/bits/socket.h
/* Types of sockets. */
enum __socket_type
{
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */
#define SOCK_STREAM SOCK_STREAM //可靠的,面向连接的流socket,默认为TCP
SOCK_DGRAM = 2,/* Connectionless, unreliable datagrams of fixed maximum length. */
#define SOCK_DGRAM SOCK_DGRAM /不可靠的,面向无连接的数据报socket,默认为UDP
SOCK_RAM = 3, /* Raw protocol interface. */ //原始套接口
.......
};
Linux描述了6种socket类型,最基本的socket类型为面向连接的数据流方式和面向无连接的数据报方式。
面向连接的数据流方式:此类型的套接字是可靠的,在这种套接字中,数据传送和发送顺序一致。 在传输数据之前,通信双方需要建立可靠的链路,在传送过程中,数据作为字节流传输。采用这种方式传输数据的可靠性高,如TCP。
面向无连接的数据报方式:此类型的套接字是不可靠的,在这种套接字中,数据传送和发送顺序可能不- -致。在发送和接收进程之间没有逻辑连接,每个数据报的发送和处理都是独立的,不同的数据报可以采用不同的路由路径到达目的地,如UDP。
第3个参数标识采用协议簇中的哪一种协议。 如果将其设置为0,让系统自动选择默认协议,但原始套接口需要指定具体的协议。
使用 socket() 创建的socket 没有任何约束的,它没有与具体的端口号相关联,在服务器端,需要使用bind函数绑定该套接字。bind 函数声明如下:
//come from /usr/include/bits/socket.h
/* Give the socket FD the local address ADDR (which is LEN bytes long). */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
此函数将指定socket与对应网络地址(含有IP和箱口信息)绑定,如果执行成功,将返回0,如果执行失败,将返回-1。
第1个参数是用于绑定本地IP信息的文件描述符。
第2个参数是一个指向sockaddr结构的指针,标识绑定的本地地址信息,如果是IP信息,则要求IP地址必须为本机IP地址,端口必须为一个未占用的本地端口。sockaddr 数据结构定义如下:
//come from /usr/include/linux/socket.h
# define __CONST_SOCKADDR_ARG __const struct sockaddr *
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx*/ //协议簇
char sa_data[14]; /* 14 bytes of protocol address*/ //协议地址
};
struct sockaddr只是提供地址类型规范,根据不同的应用,sockaddr 需要选用不同的类型,如下所述。
如果是UNIX域套接字,即本机通信的套接字,socket 需要与一个本地socket文件进行绑定。因此,sockaddr 结构体应该选用以下定义:
/ /come from /usr/include/sys/un.h
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
#define __SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##family //##为宏联接struct sockaddr_un
{
_SOCKADDR_ COMMON (sun_); //协议 AF_UNIX
char sun_path[108]; /* Path name. */ //文件路径名
};
因为是本机通信,因此所选用的协议为AF_UNIX (或AF_LOCAL),其通信需要依靠本地socket类型的文件,因此,第2个成员为socket所使用临时文件路径,此文件名不能与系统文件名冲突,即使用前该文件不能存在, 使用完后最好将该文件删除。
如果是IPV4网络通信, socket需要与本机可用的IP地址和端口绑定,因此,sockaddr结构体应该选用以下定义:
//come from /usr/include/netinet/in.h
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); //协议AF__INET
in_port_t sin_port; /* Port number. */ //端口号
struct in_addr sin_addr; /* Internet address. */ //IP 地址
/* Pad to size of 'struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
_SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_ addr) ]; //预留位,以适应struct sockaddr位
};
第3个参数是绑定的地址长度,一般使用sizeof求得。因为有多种地址类型,所以需要提示地址大小。
绑定了IP及端口信息的socket对象还不能进行TCP方式的通信,因为当前还没有能力监听网络请求。因此,对于面向连接的应用来说,服务器端需要调用listen()函数使该socket对象监听网络。listen函数声明如下:
//come from /usr/include/sys/socket.h
extern int listen (int _fd, int __n)
如果执行成功,此函数将返回0,如果执行失败,将返回-1。
第1个参数是绑定了IP及端口信息的socket文件描述符。
第2个参数为请求排队的最大长度。当有多个客户端程序和服务器端相连时,此值表示可以使用的处于等待的队列长度。
listen函数将绑定的socket文件描述符变为监听套接字,此时,服务器已经准备接收客户端连接请求了。
如果服务器已经监听网络,且客户端创建了socket对象,则客户端可以使用connet函数与服务器端建立连接了。conneet 函数声明如下:
//come from /usr/include/sys/socket.h
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
其第1个参数为socket返回的文件描述符。第2个参数储存连接的目的主机地址(包括IP地址和端口),第3个参数为该地址的长度。
若执行成功,此函数将与地址为addr的服务器建立连接,并返回0,如若失败则返回-1。
如果服务器端监听到客户端的连接请求,则需要调用accept函数接受请求,如果没有监听到客户端的连接请求,此函数将处于阻塞状态。accept 函数声明如下:
//come from /usr/include/sys/socket.h
extern int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len);
第1个参数是监听网络后的socket文件描述符。
第2参数为struct sockaddr 类型的地址空间首地址。
第3个参数为该段地址空间长度,用来存储客户端的IP地址和端口信息,以便为客户端返回数据。
需要注意的是,如果执行成功,此函数将返回一个新的文件描述符以标识该连接,从而使原来的文件描述符可以继续监听网络等待新的连接,这样便可以实现多客户端。如果执行失败,将返回-1.
socket对象是一类特殊的文件, 因此可以使用Linux 系统 I/O系统调用 read 函数来读 socket 对象数据,write 函数向 socket 对象写入数据。这两个函数声明如下:
//come from /usr/include/unistd.h
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur; //读文件内容
extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wuZ; //写文件内容
这两个函数对socket的读写操作默认以阻塞的方式进行。
此外,Linux 还提供了send()和recv()函数来专门实现面向连接的socket对象读写操作。
send()函数用来发送数据,具体声明如下:
//come from /usr/include/sys/socket.h
extern ssize_t send (int __fd, __const void *__buf, size_t __n, int __flags);
第1个参数为发送的目标socket对象。第2个参数为欲发送的数据位置。
第3个参数为数据的大小。第4个参数操作flags,支持的值为0或MSG_OOB (发送带外数据)等。在使用send()函数时将flags设置为0的与调用write()的行为完全相同。
如果执行成功,此函数将返回发送数据的大小,如果失败,将返回-1。
//come from /usr/include/sys/socket.h
extern ssize_t recv (int __fd, void *__buf, size_t __ n, int __flags);
此函数的参数含义类似于send()函数各参数含义,其将从fd所指的socket中读取n字节数据到buf中。如果执行成功,此函数将返回接收数据的大小,如果失败,将返回-1。
在通信结束后,需要关闭socket对象,一种方法是直接使用 close 函数,此函数声明如下:
//come from /usr/include/unistd.h
/*Close the file descriptor FD. */
extern int close (int __fd);
另一种方法是调用shutdown()函数来关闭,其有更大的灵活性,shutdomn()函数可以关闭全部或者socket的一端, 其函数声明如下:
//come from /usr/include/sys/socket. h
/* Shut down all or part of the connection open on socket FD.
HOW determines what to shut down :
SHUT_RD = No more receptions; //不再接收
SHUT_WR = No more transmissions; //不再发送
SHUT_RDWR = No more receptions or transmissions. //接收和发送都不再执行
Returns 0 on success, -1 for errors. */
extern int shutdown (int __fd, int __how)
TCP连接是双向的(是可读写的),当使用close()时, 会把读写通道都关闭,有时希望只关闭一个方向,这时需要使用shutdown。系统提供了以下3种关闭方式。
使用getsockmame()函数将获得一个套接字 (这个套接口至少完成了绑定本地IP地址)的本地地址。如果成功则返回0,如果发生错误则返回-1:
extern int getsockname (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len)
第1个参数为欲读取信息的socket文件描述符,第2、3个参数分别为存储地址的内存空间的地址和大小。
使用getpeername()函数将取得一个已经连接上的套接字的远程信息,比如IP地址和端口:
extern int getpeername (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len)