Unix网络编程 之 基本套接字调用(一)

Unix/Linux支持伯克利风格的套接字编程,它同时支持面向连接和面向无连接类型的套接字。

套接字最常用的一些系统调用:

socket()

bind()

connect()

listen()

accept()

send()

recv()

sendto()

recvfrom()

close()

shutdown()

setsockopt()

getsockopt()

getpeername()

getsockname()

gethostbyname()

gethostbyaddr()

getservbyname()

getservbyport()

getprotobyname()

fcntl()

下面详解这些系统调用。

1、socket()函数

   
#include <sys/socket.h>

   
/*成功返回非负描述符,否则返回-1*/

   
int socket(int family, int type, int protocol);


中,family参数指明协议族,常用的family值有AF_INET(IPv4协议)、AF_INET6(IPv6协议)、
AF_LOCAL(Unix域协议)、AF_ROUTE(路由套接字)和AF_KEY(密钥套接字)。该参数也往往被称为协议域。注意,后两种仅适用于原
始套接字。

type参数指明套接字类型,如SOCK_STREAM(字节流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_SEQPACKET(有序分组套接字)以及SOCK_RAW(原始套接字)等。

protocol参数可以设置为IPPROTO_CP(TCP传输协议)、IPPROTO_UDP(UDP传输协议)或IPPROTO_SCTP(SCTP传输协议),同时该参数也可设置为0,以选择所给定family和type组合的系统默认值。

socket()函数在成功时返回一非负整数,它与文件描述符类似,我们称之为套接字描述符(Socket Descriptor),简称sockfd。

那么,此函数的作用是什么呢?socket函数通过我们设定的协议族、套接字类型和传输协议参数来创建底层网络文件,为进行网络通信做准备。

2、bind()函数

bind()函数把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合。

   
#include <sys/socket.h>

   
/*成功返回0,否则返回-1*/

   
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);

参数myaddr指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度。


果一个TCP客户或服务器未调用bind()捆绑一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口。让内核选择
临时端口对于TCP客户来说是正常的,但对TCP服务器来说极为罕见,因为服务器是通过它们的众所周知的端口被大家所认识的。

进程可以通过bind()函数把一个特定的IP地址绑定到其套接字上,不过此IP地址必须属于其所在主机的网络接口之一。对于TCP客户,这就为该套接字所发送的数据报文指定了源IP地址。对于TCP服务器,这就限定该套接字只接收那些目的地址为此IP地址的客户连接。

对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。那么,当我们未指定端口号或IP地址时,系统调用会如何处理呢?

一般而言,不指定端口号,bind()函数默认为0,不指定IP地址,函数默认为通配地址。


进程指定


结果


IP地址


端口port


通配地址


0


内核选择IP地址和端口


通配地址


非0


内核选择IP地址,进程指定端口


本地IP地址


0


进程指定IP地址,内核选择端口


本地IP地址


非0


进程指定IP地址和端口

注:通配地址为INADDR_ANY。

当使用socket()函数得到套接字描述符后,依情况需要将socket绑定主机上的端口:

如果为服务器进程,需要在端口进行监听(listen)操作,等待连接请求时,往往需要进行bind操作,而且这个端口应该是众所周知的;

如果为客户端进程,需要向远端服务器发起连接(connect)请求,这时,绑定端口是可选的。

附:端口号

TCP、UDP和SCTP三种传输协议使用16位端口号来区分进程。

端口号被划分为以下三段:

*众所周知的端口(0-1023);

*已登记的端口(registered port,1024-49151);

*动态(dynamic)或私用(private)端口(49152-65535)。

3、connect()函数

TCP客户端用connect()函数来建立与TCP服务器的连接。

   
#include <sys/socket.h>

   
/*成功返回0,出错返回-1*/

   
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。

客户端在调用connect()函数时,没有必要调用bind()函数。我们并不在乎我们本地用什么端口来进行通信,我们在乎的是客户端需要连接到远端服务器的哪个端口。当我们未调用bind()函数时,内核自动选择一个未被使用的本地端口。

关于connect()具体是如何工作的,会在网络协议的TCP三次握手时详细介绍。

4、listen()函数

   
#include <sys/socket.h>

   
/*成功返回0,否则返回-1*/

   
int listen(int sockfd, int backlog);

listen()函数仅由TCP服务器调用,它主要完成两件事:

*当socket()函数创建一个套接字是,其被设为主动套接字,也就是说,它是一个将调用connect()函数来主动发起连接的客户端套接字。listen()函数把一个未连接的主动套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。

*backlog规定了内核应该为相应的套接字排队的最大连接个数。

本函数通常应该在socket()和bind()函数之后,并在调用accept()函数之前调用。

这里,需要理解backlog参数:

内核为任何一个给定的监听套接字维护两个队列:

(1)未完成连接队列(incomplete connection queue),这些套接字处于SYN_RCVD状态;

(2)已完成连接队列(completed connection queue),每个已完成TCP三路握手过程,这些套接字处于ESTABLISHED状态。

下图描绘了监听套接字的两个队列。

每当在未完成队列创建一项时,来自监听套接字的参数就复制到即将建立的连接中。


来自客户的SYN到达服务器时,服务器TCP在未完成连接队列中创建一个新项,然后相应以三路握手的服务器的SYN响应,其中捎带对客户SYN的ACK。
这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或该项超时为止。如果三路握手正常完成,该项就从未完成
队列移至已完成连接队列的队尾。当进程调用accept()时,已完成连接队列中的队头项将返回到进程,如果该队列为空,那么进程将被投入睡眠,直到
TCP在该队列中放入一项才唤醒它。

如果一个客户的SYN到达时,两个队列是满的,那么TCP就会忽略该分节,但不会发送RST。这样做有一个好处:两队列是满的的情况只是暂时的,如果服务器TCP不发送RST,那么客户端TCP就会重发SYN,这样,可能不久就能在这些队列中找到可用空间。

5、accept()函数

#include <sys/socket.h>

      /*成功返回非负描述符,出错返回-1*/

   
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);


数cliaddr和addrlen用来返回已连接的对端进程(客户端)的协议地址。addrlen是值-结果参数:调用前,我们将有*addrlen所引
用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构内的确切字节数。

如果accept成功,其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接,我们称之为已连接套接字(connected socket)。


函数最多返回三个值:一个既可能是新套接字描述符也可能是出错指示的整数,客户进程的协议地址(由cliaddr指针所指)以及该地址的大小(由
addrlen指针所指)。如果我们对是哪个主机连接了该服务器(客户协议地址)不感兴趣,那么可以把cliaddr和addrlen均置为空指针。

6、send()和recv()函数

这两个函数时最基本的,通过连接的套接字流进行通信的函数。

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

send()参数含义如下:

sockfd代表与远程程序连接的套接字描述符;

buff指针指向发送信息的字符串;

nbytes指发送信息的长度;

flags指发送标记。

send()函数在调用后返回它真正发送数据的长度。但是,此发送数据可能少于参数指定的长度。如果发生错误,则返回-1,错误代码存储在全局标量errno中。

#include<sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);

recv()参数含义如下:

sockfd指读取数据的套接字描述符;

buff指针指向存储数据的内存缓存区域;

nbytes是缓存区的最大尺寸;

flags是发送标记。

recv()返回它所真正接收到的长度,也就是存储到buf中数据的长度。如果返回-1则代表发生了错误(比如网络意外中断,对方关闭了套接字连接等),全局变量errno存储了错误代码。

7、sendto()和recvfrom函数

这两个函数时进行无连接的UDP通信时使用的。使用这两个函数,则数据会在没有建立过任何连接的网络上传输。在这里,由于数据报套接字无法对远程主机建立连接,因此,我们在发送数据前需要知道远端主机的IP地址和端口号。

#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, const struct sockaddr *from, socklen_t *addrlen);

前三个参数sockfd、buff和nbytes:套接字描述符、指向读入写出缓冲区的指针和读写字节数。

sendto的to参数指向一个含有数据报接收者的协议地址的套接字地址结构,其大小由addrlen参数指定。recvfrom的from参数指向一个将由该函数在返回时填写数据包发送者的协议地址的套接字地址结构。

注意:sendto的最后一个参数是整数值,而recvfrom的最后一个参数是一个指向整数值的指针(即值-结果参数)。

recvfrom
的最后两个参数类似于accept的最后两个参数,返回时其中套接字地址结构的内容告诉我们是谁发送了数据报(UDP情况下)或是谁发起了连接(TCP情
况下)。sendto的最后两个参数类似于connect的最后两个参数,调用时其中套接字结构被我们填入数据报将发往(UDP情况下)或与之建立连接
(TCP情况下)的协议地址。

这两个函数都把所读写的数据的长度作为函数返回值。

注意:recvfrom和sendto都可以用于TCP,尽管通常没有理由这样做。

8、close()和shutdown()函数

Unix使用close函数和shutdown函数来关闭套接字,并终止TCP连接。

#include <unistd.h>

   
int close(int sockfd);

   
/*若成功则返回0,出错返回-1*/

close
一个TCP套接字的默认行为是把该套接字标记为关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是说它不能再作为read和
write的第一个参数。然而,TCP将尝试发送已排队等待发送到对端的数据,发送完毕后发生的是正常的TCP连接终止序列。

   
#include <sys/socket.h>

   
int shutdown(int sockfd, int how);

注意其中的how参数。0表示不允许以后数据的接收操作,1表示不允许以后数据的发送操作,2表示和close()一样,不允许以后的任何数据操作。

附加内容:

send/recv与write/read函数的区别

recv和send函数提供了和read和write差不多的功能,但是它们提供了第四个参数来控制读写操作。

      ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

      ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);

前面的三个参数和read,write相同,第四个参数能够是0或是以下的组合:


flags


说明


recv


send


MSG_DONTROUTE


绕过路由表查找


*


MSG_DONTWAIT


仅本操作阻塞


*


*


MSG_OOB


发送或接收带外数据


*


*


MSG_PEEK


窥看外来消息


*


MSG_WAITALL


等待所有数据


*

如果flags为0,则和read,write一样的操作。

Unix网络编程 之 基本套接字调用(一),布布扣,bubuko.com

时间: 2024-08-09 12:53:47

Unix网络编程 之 基本套接字调用(一)的相关文章

Unix网络编程--卷一:套接字联网API 读书笔记

UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 1.简介 2.传输层:TCP.UDP和SCTP 3.套接字编程简介 4.基本TCP套接字编程 5.TCP客户/服务器程序例子 6.I/O复用:select和poll函数 7.套接字选项 8.基本UDP套接字编程 9.基本SCTP套接字编程 10.SCTP客户/服务器程序例子 11.名字与地址转换 12.IPV4与IPV6互操作性 13.守护进程和

unix网络编程之基本套接口编程

第一章  套接口编程简介 IPv4套接口地址:"网际套接口地址结构" 通用套接口地址结构: 套接口函数被定义为采用指向通用套接口地址结构的指针,这要求对这些函数的任何调用都必须将指向特定于协议的套接口地址结构的指针类型转换成指向通用套接口地址结构的指针: 套接口地址结构的比较: 值-结果参数: 当把套接口地址结构传递给套接口函数时,总是通过指针来传递的,即传递的是一个指向结构的指针. 1, 从进程到内核传递套接口地址结构有3个函数:bind.connect.sendto,这3个函数的一

UNIX网络编程:socket套接字(TCP与UDP)

套接字简介: 套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程.应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题.凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行,Linux所提供的功能(如打印服务,ftp等)通常都是通过套接字来进行通信的,套接字的创建和使用与管道是有区别的,

Unix网络编程随手记——套接字接口函数

套接字接口(socket interface)是一组函数,它们和Unix I/O函数结合起来,用以创建网络应用.大多数现代系统上都实现套接字接口,包括所有的Unix变种.Windows和Macintosh. 1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: 1 struct sockaddr 2 { 3 unsigned short sa_family; /* address族, AF_xxx */ 4 char sa_data[14]; /* 14

【Unix网络编程】chapter3 套接字编程简介

chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数,不过这两个新函数inet_pton和inet_ntop同时适用于IPv4和IPv6. 3.2 套接字地址结构 sockaddr_ 3.2.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为"网际套接字地址结构",它以sockaddr_in命令,定义在<netinet/in.

【Unix网络编程】chapter3套接字编程简介

3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数,不过这两个新函数inet_pton和inet_ntop同时适用于IPv4和IPv6. 3.2 套接字地址结构 sockaddr_ 3.2.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为"网际套接字地址结构",它以sockaddr_in命令,定义在<netinet/in.h>头文件中 struc

unix网络编程卷1:套接字联网 源码编译

QUICK AND DIRTY Execute the following from the src/ directory: 一: ./configure # try to figure out all 结果是: ........ checking for struct addrinfo... yes checking for struct if_nameindex... yes checking for struct sockaddr_dl... no checking for struct

Unix网络编程 高级IO套接字设置超时

我们知道,对于一个套接字的读写(read/write)操作默认是阻塞的,如果当前套接字还不可读/写,那么这个操作会一直阻塞下去,这样对于一个需要高性能的服务器来说,是不能接受的.所以,我们可以在进行读写操作的时候可以指定超时值,这样就读写操作就不至于一直阻塞下去. 在涉及套接字的I/O操作上设置超时的方法有三种: 1:调用alarm,它在指定的超时期满时产生SIGALRM信号.这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用. 2:在select中阻

Unix网络编程_卷1卷2

1. UNIX 网络编程(第2版)第1卷:套接口API和X/Open 传输接口API PDFhttp://www.linuxidc.com/Linux/2014-04/100155.htm UNIX网络编程卷1:套接字联网API(第3版) 中文高清带完整书签 PDFhttp://www.linuxidc.com/Linux/2014-04/100222.htm UNIX网络编程.卷2:进程间通信(第2版)http://www.linuxidc.com/Linux/2013-01/77936.ht