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一样的操作。

PS.在下一系列博客,将会涉及到具体的网络编程例子。

时间: 2024-08-30 22:00:07

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

UNIX网络编程:通用套接字选项

1. SO_BROADCAST 套接字选项 本选项开启或禁止进程发送广播消息的能力.只有数据报套接字支持广播,并且还必须是在支持广播消息的网络上(例如以太网,令牌环网等).我们不可能在点对点链路上进行广播,也不可能在基于连接的传输协议(例如TCP和SCTP)之上进行广播. 2. SO_DEBUG 套接字选项 本选项仅由TCP支持.当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接受的所有分组保留详细跟踪信息.这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查. 3

unix网络编程问题(初学者)

问题描述 unix网络编程问题(初学者) 除了头文件和打印外,我都是照着书敲的,但是结果不对,希望帮我解决,谢谢啦!!!书上的正确结果是一串表示时间的字符 问题:connect error 在建立服务器的连接出错输入:./get_tima 192.168.154.130(ip地址我用ifconfig得到的,我也用127.0.0.1做过一样得不到想要的结果)结果:connect errorread error 出处:unix网络编程 1.2-一个简单的时间获取客户程序 #include#inclu

select-调试信息无法打印 unix网络编程

问题描述 调试信息无法打印 unix网络编程 如代码注释信息所示,应该是缓冲区的问题,但是stderr是无缓冲区的,而且也使用了fflush函数,也没用.希望能有人指点指点,谢谢 #include "globle.h" #define port 8082 int main( int argc, char *argv[] ){ fprintf(stderr, "11111111111n");//无法打印 fflush(stdout); int listenfd,soc

UNIX网络编程之旅-配置unp.h头文件环境

最近在学习Unix网络编程(UNP),书中steven在处理网络编程时只用了一个#include "unp.h"  相当有个性并且也很便捷 于是我把第三版的源代码编译实现了这个过程,算是一种个性化的开发环境的搭建吧,顺便把过程记录下来,以便自己以后查阅.   首先去网上找到源代码包unpv.13e.tar.gz 一找一大堆 解压缩到你的某个目录,unpv13e里面大致有这些目录 ├── aclocal.m4 ├── advio ├── bcast ├── config.guess ├─

《UNIX网络编程 卷1:套接字联网API(第3版)》——2.6 TCP连接的建立和终止

2.6 TCP连接的建立和终止 为帮助大家理解connect.accept和close这3个函数并使用netstat程序调试TCP应用,我们必须了解TCP连接如何建立和终止,并掌握TCP的状态转换图. 2.6.1 三路握手建立一个TCP连接时会发生下述情形. (1)服务器必须准备好接受外来的连接.这通常通过调用socket.bind和listen这3个函数来完成,我们称之为被动打开(passive open). (2)客户通过调用connect发起主动打开(active open).这导致客户T

《UNIX网络编程 卷1:套接字联网API(第3版)》——导读

**前言**本书面向的读者是那些希望自己编写的程序能使用称为套接字(socket)的API进行彼此通信的人.有些读者可能已经非常熟悉套接字了,因为这个模型几乎已经成了网络编程的同义词,但有些读者可能仍需要从头开始学习.本书想达到的目标是向大家提供网络编程指导.这些内容不仅适用于专业人士,也适用于初学者:不仅适用于维护已有代码,也适用于开发新的网络应用程序:此外,还适用于那些只是想了解一下自己系统中网络组件的工作原理的人. 书中的所有示例都是在Unix系统上测试通过的真实的.可运行的代码.但是,考

《UNIX网络编程 卷1:套接字联网API(第3版)》——2.3 用户数据报协议(UDP)

2.3 用户数据报协议(UDP) UDP是一个简单的传输层协议,在RFC 768[Postel 1980]中有详细说明.应用进程往一个UDP套接字写入一个消息,该消息随后被封装(encapsulating)到一个UDP数据报,该UDP数据报进而又被封装到一个IP数据报,然后发送到目的地.UDP不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数据报只到达一次. 我们使用UDP进行网络编程所遇到的问题是它缺乏可靠性.如果一个数据报到达了其最终目的地,但是

《UNIX网络编程 卷1:套接字联网API(第3版)》——1.3 协议无关性

1.3 协议无关性 图1-5中的程序是与IPv4协议相关的:我们分配并初始化一个sockaddr_in类型的结构,把该结构的协议族成员设置为AF_INET,并指定socket函数的第一个参数为AF_INET. 为了让图1-5中的程序能够在IPv6上运行,我们必须修改这段代码.图1-6所示的是一个能够在IPv6上运行的版本,其中改动之处用加粗的等宽字体突出显示. 我们只修改了程序的5行代码,得到的却是另一个与协议相关的程序:这回是与IPv6协议相关的.更好的做法是编写协议无关的程序.图11-11将

《UNIX网络编程 卷1:套接字联网API(第3版)》——1.8 BSD网络支持历史

1.8 BSD网络支持历史 套接字API起源于1983年发行的4.2BSD操作系统.图1-15展示了各种BSD发行版本的发展史,并注明了TCP/IP的主要发展历程.1990年面世的4.3BSD Reno发行版本随着OSI协议进入BSD内核而对套接字API做了少量的改动. 图1-15中从4.2BSD往下到4.4BSD的通路展示了源自Berkeley计算机系统研究组(Computer Systems Research Group,CSRG)的各个版本,它们要求获取者已拥有Unix的源代码许可权.然而