Berkeley套接字
✍ dations ◷ 2025-08-18 11:00:43 #Berkeley套接字
伯克利套接字(英语:Internet Berkeley sockets) ,又称为BSD 套接字(BSD sockets)是一种应用程序接口(API),用于网络套接字( socket)与Unix域套接字,包括了一个用C语言写成的应用程序开发库,主要用于实现进程间通讯,在计算机网络通讯方面被广泛使用。
Berkeley套接字(也作BSD套接字应用程序接口)刚开始是4.2BSD Unix操作系统(于1983发布)的一套应用程序接口。然而,由于AT&T的专利保护着UNIX,所以只有在1989年伯克利大学才能自由地发布自己的操作系统和网络库。
Berkeley套接字应用程序接口形成了事实上的网络套接字的标准精髓。 大多数其他的编程语言使用与这套用C语言写成的应用程序接口 类似的接口。 这套应用程序接口也被用于Unix域套接字(Unix domain sockets),后者可以在单机上为进程间通讯(IPC)的接口。
这种基于流的传输层接口(TLI)为套接字应用程序接口提供了一种选择。 不过,最近提供TLI应用程序接口的的系统同时也提供Berkeley套接字应用程序接口。
Berkeley套接字接口,一个应用程序接口(API),使用一个Internet套接字的概念,使主机间或者一台计算机上的进程间可以通讯。 它可以在很多不同的输入/输出设备和驱动之上运行,尽管这有赖于操作系统的具体实现。 接口实现用于TCP/IP协议,因此它是维持Internet的基本技术之一。 它是由加利福尼亚的伯克利大学开发,最初用于Unix系统。 如今,所有的现代操作系统都有一些源于Berkeley套接字接口的实现,它已成为连接Internet的标准接口。
套接字接口的接入有三个不同的级别,最基础的也是最有效的就是raw socket级别接入。 很少的应用程序需要在外向通讯控制的这个级别接入,所以raw socket级别是只为了用于开发计算机Internet相关技术的。 最近几年,大多数的操作系统已经实现了对它的全方位支持,包括Windows XP。
由于Berkeley套接字是第一个socket,大多数程序员很熟悉它们,所以大量系统把伯克利套接字作为其主要的网络API。一个不完整的列表如下:
Berkeley套接字接口的定义在几个头文件中。这些文件的名字和内容与具体的实现之间有些许的不同。 大体上包括:
这个列表是一个Berkeley套接字API库提供的函数或者方法的概要:
更多的细节如下给出。
socket()
为通讯创建一个端点,为套接字返回一个文件描述符。 socket() 有三个参数:
如果发生错误,函数返回值为-1。 否则,函数会返回一个代表新分配的描述符的整数。
int socket(int domain, int type, int protocol);
bind()
bind()
为一个套接字分配地址。当使用socket()
创建套接字后,只赋予其所使用的协议,并未分配地址。在接受其它主机的连接前,必须先调用bind()
为套接字分配一个地址。bind()
有三个参数:
如果发生错误,函数返回值为-1,否则为0。
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
listen()
当socket和一个地址绑定之后,listen()
函数会开始监听可能的连接请求。然而,这只能在有可靠数据流保证的时候使用,例如:数据类型(SOCK_STREAM
, SOCK_SEQPACKET
)。
listen()函数需要两个参数:
一旦连接被接受,返回0表示成功,错误返回-1。
原型:
int listen(int sockfd, int backlog);
accept()
当应用程序监听来自其他主机的面对数据流的连接时,通过事件(比如Unix select()系统调用)通知它。必须用 accept()
函数初始化连接。 accept()
为每个连接创立新的套接字并从监听队列中移除这个连接。它使用如下参数:
返回新的套接字描述符,出错返回-1。进一步的通信必须通过这个套接字。
Datagram 套接字不要求用accept()处理,因为接收方可能用监听套接字立即处理这个请求。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
connect()
connect()
系统调用为一个套接字设置连接,参数有文件描述符和主机地址。
某些类型的套接字是无连接的,大多数是UDP协议。对于这些套接字,连接时这样的:默认发送和接收数据的主机由给定的地址确定,可以使用 send()和 recv()。返回-1表示出错,0表示成功。
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
select()
int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
- 第一个参数nfds:没有用,仅仅为与伯克利Socket兼容而提供。
- 第二个参数readfds:指定一个Socket数组,select检查该数组中的所有Socket。如果成功返回,则readfds中存放的是符合‘可读性’条件的数组成员(如缓冲区中有可读的数据)。
- 第三个参数writefds:指定一个Socket数组,select检查该数组中的所有Socket。如果成功返回,则writefds中存放的是符合‘可写性’条件的数组成员(包括连接成功)。
- 第四个参数exceptfds:指定一个Socket数组,select检查该数组中的所有Socket。如果成功返回,则cxceptfds中存放的是符合‘有异常’条件的数组成员(包括连接接失败)。
- 第五个参数timeout:指定select执行的最长时间,如果在timeout限定的时间内,readfds、writefds、exceptfds中指定的Socket没有一个符合要求,就返回0。
getsockname() 和 getpeername ()
int getsockname (SOCKET s, struct sockaddr *name, int* namelen);
getsockname函数获取已绑定(可能是未调用bind的系统自动绑定)的套接口本地协议地址。
int getpeername (SOCKET s, struct sockaddr *name, int* namelen);
getpeername函数获得与指定套接口连接的远程信息(IP:PORT)。
gethostbyname()
和 gethostbyaddr()
函数是用来解析主机名和地址的。可能会使用DNS服务或者本地主机上的其他解析机制(例如查询/etc/hosts)。返回一个指向 struct hostent的指针,这个结构体描述一个IP主机。函数使用如下参数:
出错返回NULL指针,可以通过检查 h_errno 来确定是临时错误还是未知主机。正确则返回一个有效的 struct hostent *。
这些函数并不是伯克利套接字严格的组成部分。这些函数可能是过时了,只能处理IPv4地址。在IPv6中,替代的新函数是 getaddrinfo() and getnameinfo(), 这些新函数是基于数据结构。参考<Ws2tcpip.h>。
struct hostent *gethostbyname(const char *name);struct hostent *gethostbyaddr(const void *addr, int len, int type);
setsockopt()
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
setsockopt函数用来设置套接字选项。
参数:
在socket层, 有以下一些选项:
int ioctlsocket(_In_ SOCKET s, _In_ long cmd, _Inout_ u_long *argp);
根据第二个参数的取值,设置socket I/O模式:
inet_pton与inet_ntop两个函数,在ASCII字符描述的IP地址与网络字节序的4字节IP地址之间转换。 字母"n"与"p",分别是numerical与presentation的缩写。
套接字API是Unix网络的通用接口,允许使用各种网络协议和地址。
下面列出了一些例子,在现在的 Linux 和 BSD 中一般都已经实现了。
PF_LOCAL, PF_UNIX, PF_FILE Local to host (pipes and file-domain)PF_INET IP protocol familyPF_AX25 Amateur Radio AX.25PF_IPX Novell Internet ProtocolPF_APPLETALK Appletalk DDPPF_NETROM Amateur radio NetROMPF_BRIDGE Multiprotocol bridgePF_ATMPVC ATM PVCsPF_X25 Reserved for X.25 projectPF_INET6 IP version 6PF_ROSE Amateur Radio X.25 PLPPF_DECnet Reserved for DECnet projectPF_NETBEUI Reserved for 802.2LLC projectPF_SECURITY Security callback pseudo AFPF_KEY PF_KEY key management APIPF_NETLINK, PF_ROUTE routing APIPF_PACKET Packet familyPF_ASH AshPF_ECONET Acorn EconetPF_ATMSVC ATM SVCsPF_SNA Linux SNA ProjectPF_IRDA IRDA socketsPF_PPPOX PPPoX socketsPF_WANPIPE Wanpipe API socketsPF_BLUETOOTH Bluetooth sockets
socket的通用address描述结构sockaddr是一个16字节大小的结构(2+14),sa_family可以认为是socket address family的缩写。另外的14字节是用来描述地址。当指定sa_family=AF_INET之后,sa_data的形式也就被固定了下来:最前端的2字节用于记录16位的端口,紧接着的4字节用于记录32位的IP地址,最后的8字节清空为零。
struct sockaddr{ unsigned short sa_family; char sa_data;};struct sockaddr_in //means socket address internet{ unsigned short sin_family; //sin means socket (address) internet unsigned short sin_port; struct in_addr sin_addr; char sin_zero;};struct in_addr{ unsigned long s_addr; // means source address};
使用TCP的服务器客户机举例
服务器
设置一个简单的TCP服务器涉及下列步骤:
/* Server code in C */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { struct sockaddr_in stSockAddr; int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(-1 == SocketFD) { perror("can not create socket"); exit(EXIT_FAILURE); } memset(&stSockAddr, 0, sizeof(struct sockaddr_in)); stSockAddr.sin_family = AF_INET; stSockAddr.sin_port = htons(1100); stSockAddr.sin_addr.s_addr = INADDR_ANY; if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_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(;;) { int ConnectFD = 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); return 0; }
Python实现:
from socket import *from time import ctimeHOST=''PORT=1100BUFSIZ=1024ADDR=(HOST, PORT)sock=socket(AF_INET, SOCK_STREAM)sock.bind(ADDR)sock.listen(5)while True: print('waiting for connection') tcpClientSock, addr=sock.accept() print('connect from ', addr) while True: try: data=tcpClientSock.recv(BUFSIZ) except: print(e) tcpClientSock.close() break if not data: break s='Hi,you send me : %s' %(ctime(), data.decode('utf8')) tcpClientSock.send(s.encode('utf8')) print(, ':', data.decode('utf8'))tcpClientSock.close()sock.close()
客户机
创建一个客户机连接涉及以下步骤:
/* Client code in C */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { struct sockaddr_in stSockAddr; int Res; int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (-1 == SocketFD) { perror("cannot create socket"); exit(EXIT_FAILURE); } memset(&stSockAddr, 0, sizeof(struct sockaddr_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); } else if (0 == Res) { perror("char string (second parameter does not contain valid ipaddress"); close(SocketFD); exit(EXIT_FAILURE); } if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in))) { perror("connect failed"); close(SocketFD); exit(EXIT_FAILURE); } /* perform read write operations ... */ shutdown(SocketFD, SHUT_RDWR); close(SocketFD); return 0; }
Python实现:
from socket import * HOST='192.168.1.3'PORT=1100BUFSIZ=1024ADDR=(HOST, PORT) client=socket(AF_INET, SOCK_STREAM)client.connect(ADDR)while True: data=input('>') if not data: break client.send(data.encode('utf8')) data=client.recv(self.BUFSIZ) if not data: break print(data.decode('utf8'))
使用UDP的服务器客户机举例
用户数据报协议(UDP)是一个不保证正确传输的无连接协议。 UDP数据包可能会乱序到达,多次到达或者直接丢失。但是设计的负载比TCP小。
UDP地址空间,也即是UDP端口,和TCP端口是没有关系的。
Code may set up a UDP server on port 7654 as follows:
#include <stdio.h>#include <errno.h>#include <string.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <unistd.h> /* for close() for socket */ #include <stdlib.h>int main(void){ int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in sa; char buffer; 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,(struct sockaddr *)&sa, sizeof(struct sockaddr))) { perror("error bind failed"); close(sock); exit(EXIT_FAILURE); } for (;;) { printf ("recv test....n"); recsize = recvfrom(sock, (void *)buffer, 1024, 0, (struct sockaddr *)&sa, &fromlen); if (recsize < 0) fprintf(stderr, "%sn", strerror(errno)); printf("recsize: %dn ",recsize); sleep(1); printf("datagram: %sn",buffer); }}
上面的无限循环用recvfrom()接收给UDP端口7654的数据包。使用如下参数:
同样功能的Python实现:
import socketport=7654s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#从指定的端口,从任何发送者,接收UDP数据s.bind(('',port))print('正在等待接入...')while True: #接收一个数据 data,addr=s.recvfrom(1024) print('Received:',data,'from',addr)
客户机
用UDP数据包发送一个"Hello World!" 给地址127.0.0.1(回环地址),端口 7654 。
#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <unistd.h> /* for close() for socket */ int main(int argc, char *argv){ int sock; struct sockaddr_in sa; int bytes_sent, buffer_length; char buffer; buffer_length = snprintf(buffer, sizeof(buffer), "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,(struct sockaddr*)&sa, sizeof (struct sockaddr_in)); if (bytes_sent < 0) printf("Error sending packet: %sn", strerror(errno)); close(sock); /* close the socket */ return 0;}
buffer
指定要发送数据的指针, buffer_length
指定缓存内容的大小。
同样功能的Python实现:
import socketport=7654host='localhost's=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)s.sendto(b'Hello World!',(host,port))
参见
参考资料
- ^ 存档副本. . (原始内容存档于2009-05-31).
The "de jure" standard definition of the Sockets interface is contained in the POSIX standard, known as:
Information about this standard and ongoing work on it is available from the Austin website(页面存档备份,存于互联网档案馆).
The IPv6 extensions to the base socket API are documented in RFC 3493 and RFC 3542.
本条目部分或全部内容出自以GFDL授权发布的《自由在线电脑词典》(FOLDOC)。
相关
- 闪语族闪米特语族,译作閃語族、塞姆语族或閃米特語族 ,旧称叙利亚-阿拉伯语族,是亚非语系之下的语族之一,起源于中东地区,其下属语言约有3.3亿人作为母语,分布于西亚、北非和非洲之角,也
- 蓝氏贾第鞭毛虫Lamblia intestinalisGiardia duodenalis蓝氏贾第鞭毛虫(学名:Giardia lamblia)又称蓝布尔吉亚尔氏鞭毛虫、梨形鞭毛虫,简称贾第虫。属于鞭毛虫纲,主要寄生在人体肠道内,引起腹痛
- 兰尼单抗兰尼单抗(英语:Ranibizumab,也译为雷珠单抗,商品名Lucentis)是一种单克隆抗体片段(FAB),其与贝伐单抗(bevacizumab)是从相同亲本鼠抗体获得。它比母体分子小得多,能更紧密的结合到血管
- 胰高血糖素样肽-1胰高血糖素样肽-1(GLP-1)是一种主要由肠道L细胞所产生的激素,属于一种肠促胰岛素(incretin)。其生理作用包括:GLP-1的特殊生理作用非常有助于糖尿病的治疗,是如今糖尿病学里的热门
- 侧视侧视是使用周边视觉(英语:Peripheral vision)查看暗淡物体的一种方法。它包括不直接看物件,看起来有点侧向一边,但仍继续集中注意于该物件上。在通俗天文学中讨论过这个问题,但只
- 蒂进伊萨克·蒂进(荷兰语:Isaac Titsingh,1745年1月10日-1812年2月2日),又译为德胜、铁俊甫,是一名荷兰外科医生、学者、商人与大使。在东亚的漫长职业生涯中,蒂进是荷兰东印度公司(,简称
- 巨核螺巨核螺(学名:),是新腹足目核螺科核螺属的一种。主要分布于台湾,常栖息在水深200-300米泥沙海底。
- 义利北京义利食品有限公司是一家以生产巧克力、糖果、面包为主的食品公司,现为北京一轻食品集团子公司。义利也是第一批被中华人民共和国商务部授予中华老字号称号的公司。义利是
- 赫连勃勃凤翔:413年三月-418年十月昌武:418年十一月-419年正月夏武烈帝赫连勃勃(381年-425年),字屈孑,匈奴铁弗部人,原名刘勃勃,中国十六国时期夏国建立者。勃勃是南匈奴单于的后裔,其父刘卫辰死于北魏进攻后,勃勃依靠后秦高平公没弈干,又得后秦君主姚兴赏识。及后就以后秦与魏通好而叛秦,杀害没奕干并自立,建北夏国,屡次进攻后秦。随后更乘东晋灭后秦后班师的机会占领关中。赫连勃勃曾祖父刘虎领导铁弗部,并曾与代国发生战斗,为代国所败。祖父刘务桓重整部众,重新壮大铁弗部,并受后赵封为平北将军、左贤王。父刘卫辰
- 武科瓦尔战役 亚历山大·斯皮尔科夫斯基(11月前) 日沃塔·帕尼奇(11月起) 米莱·姆尔克希奇 韦塞林·什利万查宁 姆拉登·布拉蒂奇 † 戈兰·哈季奇 安德里亚·比奥尔切维奇武科瓦尔战役 (塞尔维亚-克罗地亚语:Битка за Вуковар / Bitka za Vukovar) 是1991年8月至11月由南斯拉夫人民军及塞尔维亚族准军事部队在位于克罗地亚东部的武科瓦尔发起的共87天的攻城战。在克罗地亚战争爆发之前,武科瓦尔是一个繁荣的巴洛克建筑风格城镇,其居民主要有有克罗地亚族和塞尔维亚族。1990年,塞尔