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或UDPport号的组合。

   
#include <sys/socket.h>

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

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

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

假设一个TCP客户或server未调用bind()捆绑一个port。当调用connect或listen时,内核就要为对应的套接字选择一个暂时port。让内核选择暂时port对于TCP客户来说是正常的,但对TCPserver来说极为罕见,由于server是通过它们的众所周知的port被大家所认识的。

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

对于TCP。调用bind函数能够指定一个port号,或指定一个IP地址。也能够两者都指定。还能够都不指定。

那么。当我们未指定port号或IP地址时。系统调用会怎样处理呢?

一般而言。不指定port号,bind()函数默觉得0。不指定IP地址。函数默觉得通配地址。

进程指定

结果

IP地址

端口port

通配地址

0

内核选择IP地址和port

通配地址

非0

内核选择IP地址。进程指定port

本地IP地址

0

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

本地IP地址

非0

进程指定IP地址和port

注:通配地址为INADDR_ANY。

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

假设为server进程。须要在port进行监听(listen)操作。等待连接请求时。往往须要进行bind操作,并且这个port应该是众所周知的;

假设为client进程,须要向远端server发起连接(connect)请求。这时,绑定port是可选的。

附:port号

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

port号被划分为下面三段:

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

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

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

3、connect()函数

TCPclient用connect()函数来建立与TCPserver的连接。

   
#include <sys/socket.h>

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

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

第二个、第三个參数各自是一个指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有server的IP地址和port号。

client在调用connect()函数时,没有必要调用bind()函数。

我们并不在乎我们本地用什么port来进行通信,我们在乎的是client须要连接到远端server的哪个port。当我们未调用bind()函数时,内核自己主动选择一个未被使用的本地port。

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

4、listen()函数

   
#include <sys/socket.h>

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

   
int listen(int sockfd, int backlog);

listen()函数仅由TCPserver调用,它主要完毕两件事:

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

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

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

这里。须要理解backlog參数:

内核为不论什么一个给定的监听套接字维护两个队列:

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

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

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

每当在未完毕队列创建一项时,来自监听套接字的參数就拷贝到即将建立的连接中。

当来自客户的SYN到达server时。serverTCP在未完毕连接队列中创建一个新项,然后对应以三路握手的server的SYN响应,当中捎带对客户SYN的ACK。这一项一直保留在未完毕连接队列中,直到三路握手的第三个分节(客户对serverSYN的ACK)到达或该项超时为止。

假设三路握手正常完毕。该项就从未完毕队列移至已完毕连接队列的队尾。当进程调用accept()时。已完毕连接队列中的队头项将返回到进程。假设该队列为空,那么进程将被投入睡眠。直到TCP在该队列中放入一项才唤醒它。

假设一个客户的SYN到达时,两个队列是满的。那么TCP就会忽略该分节,但不会发送RST。这样做有一个优点:两队列是满的的情况仅仅是临时的。假设serverTCP不发送RST。那么clientTCP就会重发SYN,这样,可能不久就能在这些队列中找到可用空间。

5、accept()函数

#include <sys/socket.h>

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

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

參数cliaddr和addrlen用来返回已连接的对端进程(client)的协议地址。

addrlen是值-结果參数:调用前,我们将有*addrlen所引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构内的确切字节数。

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

本函数最多返回三个值:一个既可能是新套接字描写叙述符也可能是出错指示的整数,客户进程的协议地址(由cliaddr指针所指)以及该地址的大小(由addrlen指针所指)。假设我们对是哪个主机连接了该server(客户协议地址)不感兴趣,那么能够把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地址和port号。

#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.在下一系列博客,将会涉及到详细的网络编程样例。

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

  1. Unix网络编程--卷一:套接字联网API

    UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 0.准备环境 1.简介 2.传输层:TCP.UD ...

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

    chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数, ...

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

    chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数, ...

  4. UNIX网络编程——基本TCP套接字编程

    一.基于TCP协议的网络程序 下图是基于TCP协议的客户端/服务器程序的一般流程: 服务器调用socket().bind().listen()完成初始化后,调用accept()阻塞等待,处于监听端口的 ...

  5. 【Unix网络编程】chapter7套接字选项

    7.1 概述 有很多方法来获取和设置影响套接字的选项: getsockopt和setsockopt函数 fcntl函数 ioctl函数 7.2 getsockopt和setsockopt函数 7.3 ...

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

    我们知道.对于一个套接字的读写(read/write)操作默认是堵塞的.假设当前套接字还不可读/写,那么这个操作会一直堵塞下去,这样对于一个须要高性能的server来说,是不能接受的.所以,我们能够在 ...

  7. 【UNIX网络编程第三版】阅读笔记(一):代码环境搭建

    粗略的阅读过<TCP/IP详解>和<计算机网络(第五版)>后,开始啃这本<UNIX网络编程卷一:套接字联网API>,目前linux下的编程不算太了解,在阅读的过程中 ...

  8. Unix网络编程_卷1卷2

    1. UNIX 网络编程(第2版)第1卷:套接口API和X/Open 传输接口API PDFhttp://www.linuxidc.com/Linux/2014-04/100155.htm UNIX网 ...

  9. [转载] 读《UNIX网络编程 卷1:套接字联网API》

    原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/ 文章写的很清楚, 适合初学者 最近看了<UNIX网 ...

随机推荐

  1. Swift语言精要 - 序列化和反序列化

    在swift中你可以把一个对象转换成为数据,你所要做的就是 首先,你需要让对象实现NSObject和NSCoding协议. 其次,实现以下两个方法: encodeWithCoder init(code ...

  2. seajs 使用文档

    // seajs 的简单配置seajs.config({    base: "/scripts/",    alias: {        "jquery": ...

  3. 保存html代码

    function svcode(F) { if (document.all) { var F = $id(F); var E = window.open("", "_bl ...

  4. Linux内核配置.config文件

    在命令行中,进入顶层内核目录,并输入命令make menuconfig,就可以启动一个基于菜单的内核配置编辑器.从这里开始,你可以访问每个可用的配置参数,并生成一个定制的内核配置. 当你退出配置编辑器 ...

  5. JavaScript正则式入门

    正则式 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表通常被用来检索.替换那些符合某个模式(规 ...

  6. 用Android程序打开和关闭输入法

    一.打开输入法窗体: InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPU ...

  7. Centos 7 文件和目录管理

    查看权限在终端输入:  ls -l xxx.xxx (xxx.xxx是文件名)  那么就会出现相类似的信息,主要都是这些:  -rw-rw-r--  其中: 最前面那个 - 代表的是类型      中 ...

  8. Web网站错误提示页面和默认訪问页面设置

    1.asp.net 定制简单的错误处理页面 通常web应用程序在公布后.为了给用户一个友好界面和使用体验,都会在发生错误时跳转至一个自己定义的错误页面,而不是asp.net向用户暴露出来的具体的异常列 ...

  9. 【Linux】常见Linux默认的shell

    常见的操作系统下的shell: Linux下默认的shell是Bourne Again shell(bash) Solaris和FreeBSD下默认的是Bourne shell(sh) AIX系统下默 ...

  10. .net通过url访问服务器获取服务器返回数据

    一.url为http协议 1.普通调用: public string GetInfo(string url) { //访问http方法 string strBuff = ""; U ...