socker

字数 3225阅读 100

伯克利套接字Berkeley sockets),也称为BSD Socket。伯克利套接字的应用编程接口API)是采用C语言进程间通信,经常用在计算机网络间的通信。 BSD Socket的应用编程接口已经是网络套接字的事实上的抽象标准。大多数其他程序语言使用一种相似的编程接口。

BSD Socket作为一种API,允许不同主机或者同一个计算机上的不同进程之间的通信。它支持多种I/O设备和驱动,但是具体的实现是依赖操作系统的。这种接口对于TCP/IP是必不可少的,所以是互联网的基础技术之一。它最初是由加州伯克利大学为Unix系统开发出来的。所有现代的操作系统都都实现了伯克利套接字接口,因为它已经是连接互联网的标准接口了。

目录

1使用伯克利套接字的系统

2头文件

3API函数

3.1socket()

3.2bind()

3.3listen()

3.4accept()

3.5connect()

3.6gethostbyname() 和 gethostbyaddr()

4协议和地址

5使用TCP的服务器客户机举例

5.1服务器

5.2客户机

6使用UDP的服务器客户机举例

6.1服务器

6.2客户机

7参见

8外部链接

使用伯克利套接字的系统

由于伯克利套接字是第一个socket,大多数程序员很熟悉它们,所以大量系统把伯克利套接字作为其主要的网络API。 一个不完整的列表如下:

Windows Sockets (Winsock) ,和Berkeley Sockets很相似,最初是为了便于移植Unix程序。

Java Sockets

Python sockets

头文件

主要的头文件如下,不同的系统可能具体不同。

BSD socket 核心函数和数据结构

AF_INET 和AF_INET6 地址家族和他们对应的协议家族 PF_INET 和 PF_INET6。在互联网编程中广泛使用,包括IP地址以及TCP和UDP端口号。

PF_UNIX/PF_LOCAL 地址家族。用于运行在一台计算机上的程序间的本地通信,不用在网络中。

和IP地址相关的一些函数。

把协议名和主机名转化成数字的一些函数。

API函数

这些是伯克利套接字提供的库函数。

socket()创造某种类型的套接字,分配一些系统资源,用返回的整数识别。

bind()一般是用在服务器这边,和一个套接字地址结构相连,比如说是一个特定的本地端口号和一个IP地址。

listen()用在服务器一边,导致一个绑定的TCP套接字进入监听状态。

connect()用在客户机这边,给套接字分配一个空闲的端口号。比如说一个TCP套接字,它会试图建立一个新的TCP连接。

accept()用在服务器这边。从客户机那接受请求试图创造一个新的TCP连接,并把一个套接字和这个连接相联系起来。

send()andrecv(), orwrite()andread(), orsendto()andrecvfrom()用来接收和发送数据。

close()关闭连接,系统释放资源。

gethostbyname()andgethostbyaddr()用来解析主机名和地址。

select()is used to prune a provided list of sockets for those that are ready to read, ready to write, or that have errors.

poll()is used to check on the state of a socket in a set of sockets. The set can be tested to see if any socket can be written to, read from or if an error occurred.

getsockopt()is used to retrieve the current value of a particular socket option for the specified socket.

setsockopt()is used to set a particular socket option for the specified socket.

更多的细节在下面。

socket()

socket()为通信创造一个端点并返回一个文件描述符。socket()由三个参数:

domain, 确定协议族。例如:

PF_INET是IPv4或者

PF_INET6是IPv6.

PF_UNIX是本地(用一个文件).

type, 是下面中的一个:

SOCK_STREAM(可靠的面向连接的服务或者Stream Sockets)

SOCK_DGRAM(数据包服务或者Datagram Sockets)

SOCK_SEQPACKET(可靠的有序的分组服务),或者

SOCK_RAW(网络层的原始协议)。

protocol确定实际使用的运输层。最常见的是IPPROTO_TCP,IPPROTO_SCTP,IPPROTO_UDP,IPPROTO_DCCP。这些协议是在中定义的。如果domain和type已经确定,“0” 可以用来选择一个默认的协议。

如果出错返回-1,否则返回一个代表文件描述符的整数。

函数原型

intsocket(intdomain,inttype,intprotocol);

bind()

bind()给套接字分配一个地址。当使用socket()创造一个套接字时, 只是给定了协议族,并没有分配地址。在套接字能够接受来自其他主机的连接前,必须用bind()给它绑定一个地址。bind()由三个参数:

sockfd, 代表socket的文件描述符。

my_addr, 指向sockaddr结构体的指针,代表要绑定的地址 。

addrlen, 是sockaddr结构体的大小。

Bind()返回0表示成功,错误返回-1。

函数原型

intbind(intsockfd,conststructsockaddr*my_addr,socklen_t addrlen);

listen()

一旦一个套接字和一个地址联系之后,listen()监听到来的连接。但是这只适用于对面向连接的模式,例如 套接字类型是 (SOCK_STREAM,SOCK_SEQPACKET)。listen()需要两个参数:

sockfd,一个有效的套接字描述符。

backlog,一个整数,表示一次能够等待的最大连接数目。操作系统通常会对这个值设置上限。

一旦连接被接受,返回0表示成功,错误返回-1。

函数原型:

intlisten(intsockfd,intbacklog);

accept()

当应用程序监听来自其他主机的面对数据流的连接时,通过事件(比如Unix select()系统调用)通知它。必须用accept()函数初始化连接。 Accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。它使用如下参数:

sockfd,监听的套接字描述符

cliaddr, 指向sockaddr 结构体的指针,客户机地址信息。

addrlen,指向socklen_t的指针,确定客户机地址结构体的大小 。

返回新的套接字描述符,出错返回-1。进一步的通信必须通过这个套接字。

Datagram 套接字不要求用accept()处理,因为接收方可能用监听套接字立即处理这个请求。

函数原型:

intaccept(intsockfd,structsockaddr*cliaddr,socklen_t*addrlen);

connect()

connect()系统调用为一个套接字设置连接,参数有文件描述符和主机地址。

某些类型的套接字是无连接的,大多数是UDP协议。对于这些套接字,连接时这样的:默认发送和接收数据的主机由给定的地址确定,可以使用 send()和 recv()。 返回-1表示出错,0表示成功。

函数原型:

intconnect(intsockfd,conststructsockaddr*serv_addr,socklen_t addrlen);

gethostbyname() 和 gethostbyaddr()

gethostbyname()和gethostbyaddr()函数是用来解析主机名和地址的。可能会使用DNS服务或者本地主机上的其他解析机制(例如查询/etc/hosts)。返回一个指向struct hostent的指针,这个结构体描述一个IP主机。函数使用如下参数:

name指定主机名。例如 www.wikipedia.org

addr指向struct in_addr的指针,包含主机的地址。

len给出addr的长度,以字节为单位。

type指定地址族类型 (比如 AF_INET)。

出错返回NULL指针,可以通过检查h_errno来确定是临时错误还是未知主机。正确则返回一个有效的struct hostent *。

这些函数并不是伯克利套接字严格的组成部分。这些函数可能是过时了,新函数是getaddrinfo() and getnameinfo(), 这些新函数是基于addrinfo数据结构。

函数原型:

structhostent*gethostbyname(constchar*name);structhostent*gethostbyaddr(constvoid*addr,intlen,inttype);

协议和地址

套接字API是Unix网络的通用接口,允许使用各种网络协议和地址。

下面列出了一些例子,在现在的LinuxBSD中一般都已经实现了。

PF_LOCAL, PF_UNIX, PF_FILE

Local to host (pipes and file-domain)

PF_INET        IP protocol family

PF_AX25        Amateur Radio AX.25

PF_IPX          Novell Internet Protocol

PF_APPLETALK    Appletalk DDP

PF_NETROM      Amateur radio NetROM

PF_BRIDGE      Multiprotocol bridge

PF_ATMPVC      ATM PVCs

PF_X25          Reserved for X.25 project

PF_INET6        IP version 6

PF_ROSE        Amateur Radio X.25 PLP

PF_DECnet      Reserved for DECnet project

PF_NETBEUI      Reserved for 802.2LLC project

PF_SECURITY    Security callback pseudo AF

PF_KEY          PF_KEY key management API

PF_NETLINK, PF_ROUTE

routing API

PF_PACKET      Packet family

PF_ASH          Ash

PF_ECONET      Acorn Econet

PF_ATMSVC      ATM SVCs

PF_SNA          Linux SNA Project

PF_IRDA        IRDA sockets

PF_PPPOX        PPPoX sockets

PF_WANPIPE      Wanpipe API sockets

PF_BLUETOOTH    Bluetooth sockets

使用TCP的服务器客户机举例

服务器

设置一个简单的TCP服务器涉及下列步骤:

调用socket()建立套接字

调用Binding 把套接字绑定到一个监听端口上。在调用bind()之前, 程序必须声明一个sockaddr_in结构体,用memset()清除, and thesin_family(AF_INET), and fill itssin_port(the listening port, innetwork byte order) fields. Converting ashort intto network byte order can be done by calling the functionhtons()(host to network short).

Preparing the socket to listen for connections (making it a listening socket), with a call tolisten().

Accepting incoming connections, via a call toaccept(). This blocks until an incoming connection is received, and then returns a socket descriptor for the accepted connection. The initial descriptor remains a listening descriptor, andaccept()can be called again at any time with this socket, until it is closed.

Communicating with the remote host, which can be done throughsend()andrecv()orwrite()andread().

Eventually closing each socket that was opened, once it is no longer needed, usingclose(). Note that if there were any calls tofork(), each process must close the sockets it knew about (the kernel keeps track of how many processes have a descriptor open), and two processes should not use the same socket at once.

/* Server code in C */#include #include #include #include #include #include #include #include intmain(void){structsockaddr_in stSockAddr;intSocketFD=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);if(-1==SocketFD){perror("can not create socket");exit(EXIT_FAILURE);}memset(&stSockAddr,0,sizeof(structsockaddr_in));stSockAddr.sin_family=AF_INET;stSockAddr.sin_port=htons(1100);stSockAddr.sin_addr.s_addr=INADDR_ANY;if(-1==bind(SocketFD,(conststructsockaddr*)&stSockAddr,sizeof(structsockaddr_in))){perror("error bind failed");close(SocketFD);exit(EXIT_FAILURE);}if(-1==listen(SocketFD,10)){perror("error listen failed");close(SocketFD);exit(EXIT_FAILURE);}for(;;){intConnectFD=accept(SocketFD,NULL,NULL);if(0>ConnectFD){perror("error accept failed");close(SocketFD);exit(EXIT_FAILURE);}/* perform read write operations ... */shutdown(ConnectFD,SHUT_RDWR);close(ConnectFD);}close(SocketFD);return0;}

客户机

建立一个客户机连接涉及以下步骤:

调用socket()建立套接字。

用connect()连接到服务器, passing asockaddr_instructure with thesin_familyset toAF_INET,sin_portset to the port the endpoint is listening (in network byte order), andsin_addrset to the IP address of the listening server (also in network byte order.)

用send()和recv()或者write()和read()进行通信。

用close()终止连接。如果调用fork(), 每个进程都要用close()。

/* Client code in C */#include #include #include #include #include #include #include #include intmain(void){structsockaddr_in stSockAddr;intRes;intSocketFD=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);if(-1==SocketFD){perror("cannot create socket");exit(EXIT_FAILURE);}memset(&stSockAddr,0,sizeof(structsockaddr_in));stSockAddr.sin_family=AF_INET;stSockAddr.sin_port=htons(1100);Res=inet_pton(AF_INET,"192.168.1.3",&stSockAddr.sin_addr);if(0>Res){perror("error: first parameter is not a valid address family");close(SocketFD);exit(EXIT_FAILURE);}elseif(0==Res){perror("char string (second parameter does not contain valid ipaddress");close(SocketFD);exit(EXIT_FAILURE);}if(-1==connect(SocketFD,(conststructsockaddr*)&stSockAddr,sizeof(structsockaddr_in))){perror("connect failed");close(SocketFD);exit(EXIT_FAILURE);}/* perform read write operations ... */shutdown(SocketFD,SHUT_RDWR);close(SocketFD);return0;}

使用UDP的服务器客户机举例

用户数据报协议(UDP)是一个不保证正确传输的无连接协议。 UDP数据包可能会乱序到达,多次到达或者直接丢失。但是设计的负载比TCP小。

UDP地址空间,也即是UDP端口,和TCP端口是没有关系的。

服务器

Code may set up a UDP server on port 7654 as follows:

#include #include #include #include #include #include #include /* for close() for socket */#include intmain(void){intsock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);structsockaddr_in sa;charbuffer[1024];ssize_t recsize;socklen_t fromlen;memset(&sa,0,sizeof(sa));sa.sin_family=AF_INET;sa.sin_addr.s_addr=INADDR_ANY;sa.sin_port=htons(7654);if(-1==bind(sock,(structsockaddr*)&sa,sizeof(structsockaddr))){perror("error bind failed");close(sock);exit(EXIT_FAILURE);}for(;;){printf("recv test....\n");recsize=recvfrom(sock,(void*)buffer,1024,0,(structsockaddr*)&sa,&fromlen);if(recsize<0)fprintf(stderr,"%s\n",strerror(errno));printf("recsize: %d\n",recsize);sleep(1);printf("datagram: %s\n",buffer);}}

上面的无限循环用recvfrom()接收给UDP端口7654的数据包。使用如下参数:

指向缓存数据指针

缓存大小

标志

地址

地址结构体大小

客户机

用UDP数据包发送一个"Hello World!" 给地址127.0.0.1(回环地址),端口 7654 。

#include #include #include #include #include #include #include #include /* for close() for socket */intmain(intargc,char*argv[]){intsock;structsockaddr_in sa;intbytes_sent,buffer_length;charbuffer[200];buffer_length=snprintf(buffer,sizeofbuffer,"Hello World!");sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);if(-1==sock)/* if socket failed to initialize, exit */{printf("Error Creating Socket");exit(EXIT_FAILURE);}memset(&sa,0,sizeof(sa));sa.sin_family=AF_INET;sa.sin_addr.s_addr=htonl(0x7F000001);sa.sin_port=htons(7654);bytes_sent=sendto(sock,buffer,buffer_length,0,(structsockaddr*)&sa,sizeof(structsockaddr_in));if(bytes_sent<0)printf("Error sending packet: %s\n",strerror(errno));close(sock);/* close the socket */return0;}

buffer指定要发送数据的指针,buffer_length指定缓存内容的大小。

推荐阅读更多精彩内容