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 bytes的协议地址 */
5
6 };

sa_family 一般来说,都是“AFINET”。

sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。

为了处理struct sockaddr, 程序员建立了另外一个相似的结构 struct sockaddr_in:

struct sockaddr_in (“in” 代表 “Internet”)

1 struct sockaddr_in
2  {
3   short int sin_family; /* Internet地址族 */
4   unsigned short int sin_port; /* 端口号 */
5   struct in_addr sin_addr; /* Internet地址 */
6   unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
7 };

这个结构提供了方便的手段来访问socket address(struct sockaddr)结构中的每一个元素。

2.基本套接字调用

socket() 函数

客户端和服务器使用socket函数来创建一个套接字描述符。

socket 函数的定义是下面这样子的:

#include <sys/types.h>

#include <sys/socket.h>

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

若成功则返回非负描述符,出错返回-1。

在我们的代码中,一般总是带这样的参数来调用socket函数:

client = socket(AF_INET, SOCK_STREAM, 0);

其中,AF_INET表明我们是在使用因特网,而SOCK_STREAM表示打开的是一个TCP套接字,0表示使用缺省方式。

bind() 函数

bind()函数可以帮助你指定一个套接字使用的端口。

当你使用socket() 函数得到一个套接字描述符,你也许需要将socket 绑定上一个你的机器上的端口。

当你需要进行端口监听 listen()操作,等待接受一个连入请求的时候,一般都需要经过这一步。

如果你只是想进行连接一台服务器,也就是进行 connect() 操作的时候,这一步并不是必须的。

bind()的系统调用声明如下:

#include <sys/types.h>

#include <sys/socket.h>

int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;

参数说明:

sockfd 是由socket()函数返回的套接字描述符。

my_addr 是一个指向struct sockaddr 的指针,包含有关你的地址的信息:名称、端口和IP 地址。

addrlen 可以设置为sizeof(struct sockaddr)。

connect()函数

让我们花一点时间来假设你是一个Telnet 应用程序。你的使用者命令你建立一个套接字描述符。你遵从命令,调用了socket()。

然后,使用者告诉你连接到“166.111.69.52”的23 端口(标准的Telnet 端口)??你应该怎么做呢?

你很幸运:Telnet 应用程序,你现在正在阅读的就是套接字的进行网络连接部分:connect()。

connect() 函数的定义是这样的:

#include <sys/types.h>

#include <sys/socket.h>

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);

connect()的三个参数意义如下:

sockfd :套接字文件描述符,由socket()函数返回的。

serv_addr 是一个存储远程计算机的IP 地址和端口信息的结构。

addrlen 应该是sizeof(struct sockaddr)。

listen() 函数

listen()函数是等待别人连接,进行系统侦听请求的函数。当有人连接你的时候,你有两步需要做:

通过listen()函数等待连接请求,然后使用accept()函数来处理。(accept()函数在下面介绍)。

listen()函数调用是非常简单的。函数声明如下:

#include <sys/socket.h>

int listen(int sockfd, int backlog);

listen()函数的参数意义如下:

sockfd 是一个套接字描述符,由socket()系统调用获得。

backlog 是未经过处理的连接请求队列可以容纳的最大数目。

backlog 具体一些是什么意思呢?每一个连入请求都要进入一个连入请求队列,等待

listen 的程序调用accept()(accept()函数下面有介绍)函数来接受这个连接。当系统还没有

调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog 的数值。

《深入理解计算机系统》这本书推荐一般设置为一个比较大的数,如1024.

accept()函数

函数accept()有一些难懂。当调用它的时候,大致过程是下面这样的:

l 有人从很远很远的地方尝试调用 connect()来连接你的机器上的某个端口(当然是

你已经在listen()的)。

l 他的连接将被 listen 加入等待队列等待accept()函数的调用(加入等待队列的最多

数目由调用listen()函数的第二个参数backlog 来决定)。

l 你调用 accept()函数,告诉他你准备连接。

l accept()函数将回返回一个新的套接字描述符,这个描述符就代表了这个连接!

好,这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而

第一个套接字描述符仍然在你的机器上原来的那个端口上listen()。

这时候你所得到的那个新的套接字描述符就可以进行send()操作和recv()操作了。

下面是accept()函数的声明:

#include <sys/socket.h>

int accept(int sockfd, void *addr, int *addrlen);

accept()函数的参数意义如下:

sockfd 是正在listen() 的一个套接字描述符。

addr 一般是一个指向struct sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的IP 地址和端口)

send()、recv()函数

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

send() 函数的声明:

#include <sys/types.h>

#include <sys/socket.h>

int send(int sockfd, const void *msg, int len, int flags);

send 的参数含义如下:

l sockfd 是代表你与远程程序连接的套接字描述符。

l msg 是一个指针,指向你想发送的信息的地址。

l len 是你想发送信息的长度。

l flags 发送标记。一般都设为0

函数recv()调用在许多方面都和send()很相似,下面是recv()函数的声明:

#include <sys/types.h>

#include <sys/socket.h>

int recv(int sockfd, void *buf, int len, unsigned int flags);

recv()的参数含义如下:

l sockfd 是你要读取数据的套接字描述符。

l buf 是一个指针,指向你能存储数据的内存缓存区域。

l len 是缓存区的最大尺寸。

l flags 是recv() 函数的一个标志,一般都为0 (具体的其他数值和含义请参考recv()

的man pages)。

recv() 返回它所真正收到的数据的长度。

sendto() 和recvfrom() 函数

这两个函数是进行无连接的UDP 通讯时使用的。使用这两个函数,则数据会在没有

建立过任何连接的网络上传输。因为数据报套接字无法对远程主机进行连接,想想我们在

发送数据前需要知道些什么呢?

对了!是远程主机的IP 地址和端口!

下面是sendto()函数和recvfrom()函数的声明:

#include <sys/types.h>

#include <sys/socket.h>

int sendto(int sockfd, const void *msg, int len, unsigned int flags,

const struct sockaddr *to, int tolen);

和你所看到的一样,这个函数和send()函数基本一致。

l sockfd 是代表你与远程程序连接的套接字描述符。

l msg 是一个指针,指向你想发送的信息的地址。

l len 是你想发送信息的长度。

l flags 发送标记。一般都设为0。(你可以查看send 的man pages 来获得其他的参

数值并且明白各个参数所代表的含义)

l to 是一个指向struct sockaddr 结构的指针,里面包含了远程主机的IP 地址和端口

数据。

l tolen 只是指出了struct sockaddr 在内存中的大小sizeof(struct sockaddr)。

和send()一样,sendto()返回它所真正发送的字节数(当然也和send()一样,它所真正

发送的字节数可能小于你所给它的数据的字节数)。当它发生错误的时候,也是返回 –1 ,

同时全局变量errno 存储了错误代码。

同样的,recv()函数和recvfrom()函数也基本一致。

recvfrom()的声明为:

#include <sys/types.h>

- 156 - Linux网络编程

#include <sys/socket.h>

int recvfrom(int sockfd, void *buf, int len, unsigned int flags

struct sockaddr *from, int *fromlen);

其参数含义如下:

l sockfd 是你要读取数据的套接字描述符。

l buf 是一个指针,指向你能存储数据的内存缓存区域。

l len 是缓存区的最大尺寸。

l flags 是recv() 函数的一个标志,一般都为0 (具体的其他数值和含义请参考recv()

的man pages)。

l from 是一个本地指针,指向一个struct sockaddr 的结构(里面存有源IP 地址和端

口数)。

l fromlen 是一个指向一个int 型数据的指针,它的大小应该是sizeof (struct sockaddr)。

当函数返回的时候,formlen 指向的数据是form 指向的struct sockaddr 的实际大小。

recvfrom() 返回它接收到的字节数,如果发生了错误,它就返回-1。

close()和shutdown()函数

程序进行网络传输完毕后,你需要关闭这个套接字描述符所表示的连接。实现这个非

常简单,只需要使用标准的关闭文件的函数:close()。

使用方法:

close(sockfd);

执行close()之后,套接字将不会在允许进行读操作和写操作。任何有关对套接字描述

符进行读和写的操作都会接收到一个错误。

如果你想对网络套接字的关闭进行进一步的操作的话,你可以使用函数shutdown()。

它允许你进行单向的关闭操作,或是全部禁止掉。

shutdown()的声明为:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

它的参数含义如下:

l sockfd 是一个你所想关闭的套接字描述符.

l how 可以取下面的值。0 表示不允许以后数据的接收操作;1 表示不允许以后数据的发送操作;2 表示和close()一样,不允许以后的任何操作(包括接收,发送数据)

shutdown() 如果执行成功将返回0,如果在调用过程中发生了错误,它将返回–1,全

局变量errno 中存储了错误代码。

setsockopt() 和getsockopt() 函数

Linux 所提供的socket 库含有一个错误(bug)。此错误表现为你不能为一个套接字重新启用同一个端口号,即使在你正常关闭该套接字以后。

例如,比方说,你编写一个服务器在一个套接字上等待的程序.服务器打开套接字并在其上侦听是没有问题的。无论如何,总有一些原因(不管是正常还是非正常的结束程序)

使你的程序需要重新启动。然而重启动后你就不能把它绑定在原来那个端口上了。从bind()系统调用返回的错误代码总是报告说你试图连接的端口已经被别的进程所绑定。问题

就是Linux 内核在一个绑定套接字的进程结束后从不把端口标记为未用。在大多数Linux/UNIX 系统中,端口可以被一个进程重复使用,甚至可以被其它进程使用。在Linux 中绕开

这个问题的办法是,当套接字已经打开但尚未有连接的时候用setsockopt()系统调用在其上设定选项(options)。setsockopt() 调用设置选项而getsockopt()从给定的套接字取得选项。

这里是这些调用的语法:

#include<sys/types.h>

#include<sys/socket.h>

int getsockopt(int sockfd, int level, int name, char *value, int *optlen);

int setsockopt(int sockfd, int level, int name, char *value, int *optlen);

下面是两个调用的参数说明:

l sockfd 必须是一个已打开的套接字。

l level 是函数所使用的协议标准(protocol level)(TCP/IP 协议使用IPPROTO_TCP,

套接字标准的选项实用SOL_SOCKET)。

l name 选项在套接字说明书中(man page)有详细说明。

l value 指向为getsockopt()函数所获取的值,setsockopt()函数所设置的值的地址。

l optlen 指针指向一个整数,该整数包含参数以字节计算的长度。

现在我们再回到Linux 的错误上来.当你打开一个套接字时必须同时用下面的代码段

来调用setsockopt()函数:

/* 设定参数数值 */

opt = 1; len = sizeof(opt);

/* 设置套接字属性 */

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

本文章参考了《深入理解计算机系统》和博主流云揽月的文章。

时间: 2024-11-09 15:14:34

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网络编程:socket套接字(TCP与UDP)

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

【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网络编程 高级IO套接字设置超时

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

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网络编程 之 基本套接字调用(一)

Unix/Linux支持伯克利风格的套接字编程,它同时支持面向连接和面向无连接类型的套接字. 套接字最常用的一些系统调用: socket() bind() connect() listen() accept() send() recv() sendto() recvfrom() close() shutdown() setsockopt() getsockopt() getpeername() getsockname() gethostbyname() gethostbyaddr() getse

Linux网络编程:原始套接字的魔力【上】

基于原始套接字编程 在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发层面,数据的传输特性由TCP或UDP来保证: 也就是说,对于TCP或UDP的程序开发,焦点在Data字段,我们没法直接对TCP或UDP头部字段进行赤裸裸的修改,当然还有IP头.换句话说,我们对它们头部操作的空间非常受限,只能使用它们已经开放给我们的诸如源.目的IP,源.目的端口等等. 今天我们讨论一下原始套接字的程序开发,用它作为入门协议栈的进阶跳板太合适不过了.OK闲话不多说,进入正题. 原始

(一)网络编程基础之套接字入门

套接字基础 首先,我们来思考下这样一个问题:为什么要使用套接字进行网络编程? 答:Linux环境下使用套接字进行进程之间的通信.套接字接口(socket interface)是一组函数,也是操作系统提供给应用程序的接口.在Unix系统中,套接字和Unix I/O函数结合起来,用来创建网络应用程序.(也就是说,操作系统对外只提供了套接字作为网络通信的接口,假如想进行网络通信,套接字我们用也得用,不用也得用,而且使用套接字来进行网络通信是十分通用的方法).这里最典型的就是客户端--服务器模型. 因特