UNIX Socket教程 - Socket编程
套接字是相同的或不同的计算机上进行数据交换的通信点。套接字支持UNIX,Windows,Mac和许多其他操作系统。本教程假定读者已经精通C语言编程及熟悉相关基本概念。
Socket是什么? - Socket编程
套接字允许两个不同的进程之间的通信相同的或不同的机器上。在Unix中,每个I/O操作都是由写入或读取一个文件描述符。文件描述符是一个打开的文件相关联的仅仅是一个整数,它可以是网络连接、一个文本文件中或一个终端,还可以是其他的东西。
对于程序员套接字看起来和行为很像一个较低水平的文件描述符。这是因为命令,例如read()和write()和套接字方式同样,他们做的文件和管道的工作。套接字和正常的文件描述符之间的差异发生在创建一个socket,并通过各种特殊的操作来控制一个套接字。
套接字是2.1BSD首次推出,其后细化到当前的形式-4.2BSD。套接字功能是现在目前大多数的UNIX系统版本。
哪里使用套接字?
使用Unix套接字在客户端服务器应用程序框架。一台服务器是一个过程,从客户端的请求负责完成一些功能。大部分的应用层协议,如FTP,SMTP和POP3使用套接字建立连接客户端和服务器之间,然后进行数据交换。
Socket 类型:
有四种类型可供用户使用的套接字。最常用的是前两个而最后两个很少被使用。
进程被推定为只有相同类型的套接字之间进行通信,但没有任何限制,防止不同类型的套接字之间的通信。
- Stream Sockets: 在网络环境下的传递保证。如果您发送通过流套接字三个项目“A,B,C”,他们将在同一顺序到达 - “A,B,C”。这些套接字使用TCP(传输控制协议)进行数据传输。如果传递是不可能的,发送者会收到一个错误信号。数据记录没有任何界限。
- Datagram Sockets: 传递在网络环境中是无法得到保证。他们是无连接的,因为并不需要有一个开放的流套接字连接 - 建立了一个数据包的目的地信息,并将其发送出去。他们使用UDP(用户数据报协议)。
- Raw Sockets: 为用户提供访问底层通信协议支持套接字抽象的。这些套接字通常是面向数据报,但其确切的特点是依赖于该协议所提供的接口。原始套接字并不打算为广大用户,他们主要是针对那些热衷于开发新的通信协议或获得一些更深奥的设备,现有协议已提供。
- Sequenced Packet Sockets: 它们是相似的流套接字,除了保留记录边界。此接口仅提供作为网络系统(NS)的套接字概念的一部分,并且是非常重要的,在最严重的NS应用。序列测定数据包套接字允许用户操作序列数据包协议(SPP)或互联网数据报协议(IDP)可以通过编写一个原型头沿与要被发送的所有数据,或者通过指定一个数据包或数据包的一组接头连接器上可以使用一个缺省的头部与所有传出的数据,允许用户接收传入的数据包的报头。
下一步学习什么?
在接下来的几章中,将使用使用套接字写一个服务器和客户端作为示例。如果直接想要跳转看如何编写一个客户端和服务器,这里不推荐这样做。本教程会强烈建议先学习完成前面几章,了解一些必要的基础知识,然后开始做编程。
Socket 网络地址(IP地址) - Socket编程
在我们理解实际的东西开始之前,让我们理解有关网络地址 - IP地址。
主机的IP地址或更常见的仅有的IP地址,用于识别连接到Internet的主机。 IP代表互联网协议,是指Internet层的整体网络架构的上网。
IP地址是一个32位的解释为4个8位数字或字节的数量。每个IP地址唯一地标识用户参与的网络、网络上的主机和用户网络的一类。
一个IP地址通常用点分十进制表示法的形式,如:N1.N2.N3.N4,其中每个Ni是一个十进制数介于0和255十进制(00到FF的十六进制)。
地址类:
IP地址由互联网编号分配机构(IANA)创建进行管理。有5个不同的地址类。通过检查IP地址的前4位可以决定哪个类IP地址。
- A类地址0xxx,或1~126的十进制表示
- B类地址10xx,或128~191的十进制表示
- C类地址110x,或192~223的十进制表示
- D类地址1110,或224~239 的十进制表示
- E类**地址1111, 或240 ~ 254** 十进制表示.
地址01111111或十进制的127开始,被保留环回和本地机器上的内部测试;[可以测试:应该总是能够ping通127.0.0.1] D类地址被保留用于多播,E类地址保留为将来使用。它们不应被用于主机地址。
例子:
Class | Leftmost bits | Start address | Finish address |
A | 0xxx | 0.0.0.0 | 127.255.255.255 |
B | 10xx | 128.0.0.0 | 191.255.255.255 |
C | 110x | 192.0.0.0 | 223.255.255.255 |
D | 1110 | 224.0.0.0 | 239.255.255.255 |
E | 1111 | 240.0.0.0 | 255.255.255.255 |
子网划分:
子网划分IP网络可以完成的原因有多种,包括组织,使用不同的物理介质(如以太网,FDDI,WAN等),保存的地址空间,与安全。最常见的原因是控制网络流量。
子网划分(也使用一个共同的字子网连接)中的基本概念是分割成两部分的IP地址的主机标识符部分:
- 子网地址的网络内解决自身问题;
- 一台主机的子网地址。
例如,一个常见的B类地址格式N1.N2.S.H,其中N1.N2标识B类网络,8位字段标识子网,8位H域标识的主机的子网。
Socket 网络主机名 - Socket编程
在一定数量上要记住许多主机名称是非常困难的。因此,这些主机名称一般都称为“ordinary”的名称,如takshila或nalanda。我们写的软件应用程序,找出带点的IP地址对应一个给定的名称。
被称为主机名解析的过程中,找出带点的IP地址的基础上给定的字母数字主机名。
通过特殊的软件,保存在高容量系统的主机名解析。这些系统被称为域名系统(DNS)保持映射IP地址和相应的普通名称。
/etc/hosts文件:
主机名和IP地址之间的对应关系保存在一个文件中称为主机。在大多数系统中找到此文件,在/etc 目录.
在这个文件中的内容看起来如下所示:
# This represents a comments in /etc/hosts file.
127.0.0.1 localhost
192.217.44.207 nalanda metro
153.110.31.18 netserve
153.110.31.19 mainserver centeral
153.110.31.20 samsonite
64.202.167.10 ns3.secureserver.net
64.202.167.97 ns4.secureserver.net
66.249.89.104 www.google.com
68.178.157.132 services.amrood.com
需要注意的是,可伴有一个以上的名字与给定的IP地址。这个文件是用来转换时,从IP地址到主机名和反之亦然。
一般用户不会有权编辑这个文件,所以如果想放的主机名对应IP地址,那么就需要具有超级用户权限(root)。
Socket 客户端服务器模式 架构 - Socket编程
NET应用程序的大部分使用的客户端服务器架构。这些术语指的是两个进程或两个应用程序在相互通信和交换一些信息。两个过程作为一个客户端进程,并另一个进程作为服务器。
客户端进程:
这是一个过程,这通常使得信息请求。得到回应后,这一过程可能会终止或可能会做一些其他的处理。
例如: 互联网浏览器作为一个客户端应用程序,Web服务器发送一个请求到得到一个HTML网页。
服务器进程:
它接受一个来自客户端的请求的过程。获得来自客户端的请求后会处理所需的收集所需的信息,将其发送到请求客户端。一旦这样做完成后,就又变成准备为另一个客户端。服务器进程始终等待准备用于处理传入请求。
实例: Web服务器一直等待来自互联网浏览器的请求,并尽快得到任何请求从浏览器,它拿起一个请求的HTML页面,并把它发送回该浏览器。
注意,客户端需要知道的存在服务器的地址,但是服务器并不需要在建立的连接之前知道客户端的地址。一旦建立连接后,双方都可以发送和接收信息。
2层和3层架构:
有两种类型的客户端服务器架构:
- 两层构架: 在这种架构中,客户端直接与服务器进行交互。这种类型的架构可能有一些安全漏洞和性能问题。 IE浏览器和Web服务器的两层架构。这里的安全问题都解决了使用安全套接字层(SSL)。
- 三层架构:在这个架构中,多了一个软件位于客户端和服务器之间。这中间的软件被称为中间件。中间件被用来执行所有的安全检查和重负载情况下的负载平衡。中间件需要从客户端的所有请求,并做必要的验证后,通过向服务器发出请求。然后,服务器没有所需的处理和发送响应回中间件,中间件终于通过这个响应返回给客户端。如果想实现一个3层架构,那么可以使用如Web Logic或WebSphere软件在Web服务器和Web浏览器之间的任何中间件。
服务器类型:
有两种类型的服务器上,可以有:
- 迭代服务器: 这是最简单形式的服务器的服务进程的客户端和第一个请求,然后完成后,需要从其他客户机的请求。同时,另一个客户端一直等待。
- 并发服务器:这种类型的服务器运行多个进程并发服务请求一次。因为一个进程可能需要更长的时间,但其他客户机又不能等太久。 Unix下写一个并发服务器的最简单的方法是fork一个子进程来分开处理每个客户端。
如何创建客户端:
系统调用有所不同的客户端和服务器建立连接,但两者套接字涉及的基本构造。这两个过程分别建立自己的套接字。
在客户端建立套接字所涉及的步骤如下:
- 创建一个socket可使用socket()的系统调用
- 套接字连接的服务器地址使用connect()系统调用。
- 发送和接收数据。做到这一点的方法有许多,但最简单的方法是使用 read() 和 write()
如何创建服务器:
在服务器端建立套接字所涉及的步骤如下:
- 创建一个socket可使用socket()的系统调用
- 使用bind()系统调用套接字绑定到一个地址。对于互联网上的服务器套接字,地址包括主机的端口号。
- 连接监听listen()系统调用
- 接受连接使用 accept()系统调用。此调用通常会阻塞,直到客户端与服务器连接。
- 发送和接收数据 read() 和write() 系统调用.
客户端和服务器的交互:
以下是完整的客户端和服务器的交互图:
Socket 结构 - Socket编程
有各种不同的Unix套接字编程结构,用来保存地址和端口信息和其他信息。大多数socket函数需要一个指向一个socket地址结构作为参数。在本教程中定义的结构与互联网协议的家族。
第一个结构是struct sockaddr的持有套接字信息:
struct sockaddr{
unsigned short sa_family;
char sa_data[14];
};
这是一个通用的套接字地址结构在大部分的套接字函数调用,将被传递。这里是成员字段的描述:
属性 | 值 | 描述 |
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK | This represents an address family. In most of the Internet based applications we use AF_INET. |
sa_data | Protocol Specific Address | The content of the 14 bytes of protocol specific address are interpreted according to the type of address. For the Internet family we will use port number IP address which is represented bysockaddr_in structure defined below. |
第二个结构,帮助引用套接字的元素如下:
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
这里是成员字段的描述:
属性 | 值 | 描述 |
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK | This represents an address family. In most of the Internet based applications we use AF_INET. |
sin_port | Service Port | A 16 bit port number in Network Byte Order. |
sin_addr | IP Address | A 32 bit IP address in Network Byte Order. |
sin_zero | Not Used | You just set this value to NULL as this is not being used. |
下一个结构仅用于上述结构中的一个结构域,并拥有32位的netid/主机ID。
struct in_addr {
unsigned long s_addr;
};
这里是成员字段的描述:
属性 | 值 | 描述 |
s_addr | service port | A 32 bit IP address in Network Byte Order. |
还有一个更重要的结构。这个结构是用来保持主机相关的信息。
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list
#define h_addr h_addr_list[0]
};
这里是成员字段的描述:
属性 | 值 | 描述 |
h_name | ti.com etc | This is official name of the host. For example tutorialspoint.com, google.com etc. |
h_aliases | TI | This will hold a list of host name aliases. |
h_addrtype | AF_INET | This contains the address family and in case of Internet based application it will always be AF_INET |
h_length | 4 | This will hold the length of IP address which is 4 for Internet Address. |
h_addr_list | in_addr | For the Internet addresses the array of pointers h_addr_list[0], h_addr_list[1] and so on are points to structure in_addr. |
注: h_addr被定义为h_addr_list[0],以保持向后兼容。.
下面的结构是用来保持服务和相关联的端口有关的信息。
struct servent
{
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
这里是成员字段的描述:
属性 | 值 | 描述 |
s_name | http | 这是官方的服务名称。例如SMTP,FTP POP3等。 |
s_aliases | ALIAS | 其将存放服务别名的列表。大部分的时间将被设置为NULL。 |
s_port | 80 | 这将有相关联的端口号。例如HTTP,则为80。 |
s_proto | TCP UDP | 这将被设置为所使用的协议。使用TCP或UDP网络服务。 |
套接字结构上的提示:
套接字地址结构是每一个网络程序的一个组成部分。我们分配填补在指针传递给它们的各种套接字函数。有时候,我们通过一个这样的结构指针的socket函数,它填补了的内容。
我们总是通过引用传递这些结构(即我们传递一个指针的结构,而不是结构本身),我们总是通过结构的大小作为另一个参数。
当套接字函数填充在一个结构中,长度也通过引用传递的,因此它的值由该函数可以被更新。我们称这些结果值参数。
请务必将结构体变量设置为NULL(即'\0')用memset()的bzero()函数,否则在你的结构,它可能会得到意想不到的垃圾值。
Socket 端口和服务 - Socket编程
当一个客户端程序要连接服务器时,客户端必须有识别要连接的服务器的一种方式。因此客户端知道可以连接32位网络地址的主机服务器所在的主机。但是,客户端如何识别特定的服务器在该主机上运行的进程呢?
要解决的问题是要确定一个特定的服务器一台主机上运行的进程,TCP和UDP定义一组众所周知的端口。
对于我们的目的,端口将被定义为1024和65535之间的整数。这是因为所有小于1024的端口号被认为是众所周知的 - 例如telnet使用端口23,HTTP使用80,FTP使用21,依此类推。
在文件/etc/services中可以找到网络服务端口分配。如果你正在写你自己的服务器,那么必须小心分配一个端口连接到服务器。应该确保该端口应该没有被其他的服务器分配到(占用)。
它的做法通常指定端口大于5000。但也有许多机构写自己的服务器端口号大于5000。例如雅虎信使运行端口号为:5050,5060等SIP服务器上运行
端口和服务实例:
这里是一个小的服务和相关端口列表。可以找到最新的互联网端口和相关的服务列表 IANA - TCP/IP Port Assignments.
服务 | 端口号 | 服务描述 |
echo | 7 | UDP/TCP sends back what it receives |
discard | 9 | UDP/TCP throws away input |
daytime | 13 | UDP/TCP returns ASCII time |
chargen | 19 | UDP/TCP returns characters |
ftp | 21 | TCP file transfer |
telnet | 23 | TCP remote login |
smtp | 25 | TCP email |
daytime | 37 | UDP/TCP returns binary time |
tftp | 69 | UDP trivial file transfer |
finger | 79 | TCP info on users |
http | 80 | TCP World Wide Web |
login | 513 | TCP remote login |
who | 513 | UDP different info on users |
Xserver | 6000 | TCP X windows (N.B. >1023) |
端口服务功能:
UNIX提供了以下功能从/etc/services文件获取服务名称.
- struct servent getservbyname(char name, char *proto): - 这个调用需要的服务名称和协议名称,并返回该服务对应的端口号。
- struct servent getservbyport(int port, char proto): - 此调用需要的端口号和协议名称,并返回相应的服务名称。
每个函数的返回值是一个指针,指向的结构与下面的形式:
struct servent
{
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
这里是成员字段的描述:
属性 | 值 | 描述 |
s_name | http | 这是官方的服务名称。例如SMTP,FTP POP3等。 |
s_aliases | ALIAS | 其将存放服务别名的列表。大部分的时间将被设置为NULL。 |
s_port | 80 | 这将有相关联的端口号。例如HTTP,为80。 |
s_proto | TCP UDP | 这将被设置为所使用的协议。使用TCP或UDP网络服务。 |
Socket 网络字节顺序 - Socket编程
不幸的是,所有的计算机的字节存储在相同的顺序组成的多字节值。请考虑,是由2个字节的一个16位的基于整数。有两种方法来存储这个值。
- Little Endian: 在这个方案中,低位字节存储在起始地址(A)和高位字节存储的下一个地址(A + 1).
- Big Endian: 在这个方案中的高位字节的开始地址(A),并存储在低位字节的下一个地址上存储(A+1).
因此,不同字节顺序的惯例,机器可以进行通信,互联网协议指定一个规范的字节顺序公约“在网络上传输的数据。这被称为网络字节顺序。
建立因特网套接字连接时,必须确保域sin_port和sin_addr成员sockaddr_in结构中的数据在网络字节顺序表示。
字节排序功能:
主机的内部表示,网络字节顺序之间转换数据的例程:
函数 | 描述 |
htons() | Host to Network Short |
htonl() | Host to Network Long |
ntohl() | Network to Host Long |
ntohs() | Network to Host Short |
下面是这些功能的更详细:
- unsigned short htons(unsigned short hostshort) 此功能从主机字节顺序到网络字节顺序的16位(2字节)为单位批量转换。
- unsigned long htonl(unsigned long hostlong) 此功能将32位(4字节)的数量从主机字节顺序到网络字节顺序。
- unsigned short ntohs(unsigned short netshort) 此功能从网络字节顺序,16位(2字节)为单位批量转换为主机字节顺序。
- unsigned long ntohl(unsigned long netlong) 此功能将32位数量从网络字节顺序转换为主机字节顺序。
这些功能是在转换的源代码插入到调用程序中的宏和结果。在little-endian的机器代码将改变周围的值转换为网络字节顺序。在大端机器没有插入代码,因为没有需要的功能定义为空(null).
程序来确定主机字节顺序:
请将下面的代码在一个文件byteorder.c和,然后在机器上编译并运行它。
在这个例子中,我们两个字节值0x0102储存在短整型,然后看看在连续两个字节,c[0](地址)和c[1](地址A+1),以确定字节顺序。
#include <stdio.h>
int main(int argc, char **argv)
{
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
} else{
printf("sizeof(short) = %d\n", sizeof(short));
}
exit(0);
}
这个程序产生在奔腾机器上的输出如下:
$> gcc byteorder.c
$> ./a.out
little-endian
$>
Socket IP地址函数 - Socket编程
UNIX提供各种功能调用,这将有助于操纵IP地址。这些函数将ASCII字符串和网络字节有序的互联网地址的二进制值(值存储在套接字地址结构)。
有以下三个函数调用用于IPv4寻址:
(1) int inet_aton(const char strptr, struct in_addr addrptr): 此函数调用指定的字符串转换,在互联网标准点标记,一个网络地址,并存储的地址提供的结构。转换后的地址将是网络字节顺序(字节下令从左至右)。这将返回1,如果字符串是有效的和错误0。
以下是使用示例:
#include <arpa/inet.h>
(...)
int retval;
struct in_addr addrptr
memset(&addrptr, '\0', sizeof(addrptr));
retval = inet_aton("68.178.157.132", &addrptr);
(...)
(2) in_addr_t inet_addr(const char *strptr): 此函数调用指定的字符串转换,在互联网标准点标记,一个整数值,适合用作互联网地址。转换后的地址将是网络字节顺序(字节下令从左至右)。这将返回一个32位二进制的网络字节命令IPv4地址和INADDR_NONE的错误。
以下是使用示例:
#include <arpa/inet.h>
(...)
struct sockaddr_in dest;
memset(&dest, '\0', sizeof(dest));
dest.sin_addr.s_addr = inet_addr("68.178.157.132");
(...)
(3) char *inet_ntoa(struct in_addr inaddr): 此函数调用指定Internet主机的地址转换为一个字符串在互联网标准点标记。
以下是使用示例:
#include <arpa/inet.h>
(...)
char *ip;
ip=inet_ntoa(dest.sin_addr);
printf("IP Address is: %s\n",ip);
(...)
Socket 核心函数 - Socket编程
本教程将介绍写一个完整的TCP客户端和服务器需要的套接字核心函数。
以下是完整的客户端和服务器的交互图:
要执行网络I/O,进程必须做的第一件事是调用socket函数,指定所需的通信协议类型和协议族等。
#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol);
这个调用给一个套接字描述符,可以用在以后的系统调用,-1为出错。
参数:
协议族: 指定协议族,是一个常量如下所示:
Family | 描述 |
AF_INET | IPv4 protocols |
AF_INET6 | IPv6 protocols |
AF_LOCAL | Unix domain protocols |
AF_ROUTE | Routing Sockets |
AF_KEY | Ket socket |
本教程不谈论除IPv4协议之外的其他协议。
类型: 指定类想要的套接字。它可以取下列值之一:
类型 | 描述 |
SOCK_STREAM | Stream socket |
SOCK_DGRAM | Datagram socket |
SOCK_SEQPACKET | Sequenced packet socket |
SOCK_RAW | Raw socket |
协议: 参数应设置具体的协议类型如下,或低于0的系统的默认值:
协议 | 描述 |
IPPROTO_TCP | TCP transport protocol |
IPPROTO_UDP | UDP transport protocol |
IPPROTO_SCTP | SCTP transport protocol |
connect函数使用一个TCP客户端,TCP服务器建立连接。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
这个调用返回0,则它成功地连接到服务器,否则它给-1的错误。
参数:
- sockfd: socket函数返回一个套接字描述符.
- serv_addr 是一个指向struct sockaddr的包含目的IP地址和端口.
- addrlen 设置sizeof为(struct sockaddr).
分配一个本地协议地址绑定功能的套接字。与互联网协议的协议地址是一个32位的IPv4地址或128比特的IPv6地址的组合,以及与一个16-bit的TCP或UDP端口号。仅由TCP服务器调用此函数。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr,int addrlen);
这个调用返回0,则表示它成功绑定的地址,否则它给-1的错误。
参数:
- sockfd: 是socket函数返回一个套接字描述符。
- my_addr 是一个指向struct sockaddr的包含本地IP地址和端口。
- addrlen 设置sizeof为(struct sockaddr).
可以把IP地址和端口自动设置:
端口号0值意味着系统将随机选择一个端口和IP地址INADDR_ANY值是指服务器的IP地址将被自动分配。
server.sin_port = 0;
server.sin_addr.s_addr = INADDR_ANY;
注: 不伦不类的端口和服务的教程,所有端口小于1024被保留。所以,可以设置1024以上的端口(但小于65535),同时设置端口不能正在被其他程序使用。
监听listen函数被调用时,只能由一个TCP服务器,它执行两个动作:
- 监听函数将陷入被动套接字未连接的套接字,表明内核应该接受传入的连接请求定向到该套接字。
- 这个函数的第二个参数指定连接的内核应此套接字队列的最大数目。
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
这个调用成功返回0,否则它返回-1的错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
- backlog 允许的连接数。
由TCP服务器调用accept函数返回下一个已完成连接,从完整的连接队列的前面。以下是调用的签名:
#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
这个调用返回非负描述符成功,否则 -1 为出错。返回的描述符被假定为一个客户端的套接字描述符,描述的所有读写操作的工作在客户端通信。
参数:
- sockfd: socket函数返回一个套接字描述符。
- cliaddr 是一个指向struct sockaddr,包含客户端的IP地址和端口。
- addrlen 它设置于sizeof(struct sockaddr).
发送功能是用来发送数据流套接字或连接的数据报套接字。如果想在未连接的数据报套接字发送数据,必须使用sendto()函数。
可以使用write()系统调用发送数据。此调用解释在辅助功能的教程。
int send(int sockfd, const void *msg, int len, int flags);
这个调用返回发送出去的字节数,否则将返回-1错误.
参数:
- sockfd: 是socket函数返回一个套接字描述符。
- msg 要发送的数据是一个指针。
- len 是要发送的数据(以字节为单位)长度。
- flags 设置为 0.
recv函数是用来接收数据流套接字或连接数据报套接字。如果想在未连接的数据报套接字接收数据,必须使用recvfrom()函数。.
可以使用read()系统调用来读取数据。此调用解释在辅助功能的教程。
int recv(int sockfd, void *buf, int len, unsigned int flags);
这个调用返回读入缓冲区的字节数,否则将返回-1错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
- buf 缓冲区读取信息。
- len 最大的缓冲区的长度。
- flags 设置为 0.
sendto函数用于未连接的数据报套接字发送数据。简单地说,当使用SCOKET类型为SOCK_DGRAM
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
这个调用返回发送的字节数否则将返回-1错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
- msg 要发送的数据是一个指针。
- len 是要发送的数据(以字节为单位)的长度。
- flags 设置为 0.
- to 是一个指向结构sockaddr的主机要发送数据。
- tolen is set it to sizeof(struct sockaddr).
recvfrom函数用于未连接的数据报套接字接收数据。简单地说,当使用SCOKET类型为SOCK_DGRAM时适用。
int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);
这个调用返回读入缓冲区的字节数,否则将返回-1错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
- buf 缓冲区读取信息。
- len 最大的缓冲区的长度。
- flags 被设置为0。
- from 是一个指向结构sockaddr的数据的主机被读取。
- fromlen 设置为sizeof(struct sockaddr)
close函数是用来关闭客户端和服务器之间的通信。
int close( int sockfd );
这个调用成功返回0,否则返回-1错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
shutdown函数用于正常关闭客户端和服务器之间的通信。此函数提供了更多的控制在比较close函数。
int shutdown(int sockfd, int how);
这个调用成功返回0,否则返回-1错误。
参数:
- sockfd: socket函数返回一个套接字描述符。
- how: 放入一个数字:
- 0 表示接收不允许的,
- 1 表明发送不允许
- 2 表明禁止发送和接收。如果设置为2,它与close()同样。
select函数显示指定文件的描述符是以待准备就绪读取,准备写入或有一个错误条件。
当应用程序调用recv或recvfrom被阻塞,直到数据到达该套接字。一个应用程序可以做其他有用的处理,而输入的数据流是空的。另一种情况是,当应用程序从多个套接字接收数据。
调用recv或recvfrom防止立即接收数据与其他Socket上,它的输入队列中没有数据。 select函数调用来解决这个问题,允许程序轮询所有的套接字手柄,看看他们是否有无阻塞读取和写入操作。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);
这个调用成功返回0,否则返回-1错误。
参数:
- nfds: specifies the range of file descriptors to be tested. The select() function tests file descriptors in the range of 0 to nfds-1
- readfds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to read, and on output indicates which file descriptors are ready to read. Can be NULL to indicate an empty set.
- writefds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to write, and on output indicates which file descriptors are ready to write Can be NULL to indicate an empty set.
- exceptfds :points to an object of type fd_set that on input specifies the file descriptors to be checked for error conditions pending, and on output indicates which file descriptors have error conditions pending. Can be NULL to indicate an empty set.
- timeout :poins to a timeval struct that specifies how long the select call should poll the descriptors for an available I/O operation. If the timeout value is 0, then select will return immediately. If the timeout argument is NULL, then select will block until at least one file/socket handle is ready for an available I/O operation. Otherwise select will return after the amount of time in the timeout has elapsed OR when at least one file/socket descriptor is ready for an I/O operation.
返回值选择多少文件描述符集指定的句柄,选择返回0,准备就绪I/O如果超时字段指定的时限到达时。下面的宏存在操纵一个文件描述符集:
- FD_CLR(fd, &fdset): 清除位文件描述符fd文件描述符集fdset。
- FD_ISSET(fd, &fdset): 返回一个非零值,如果该位被设置为文件描述符fd文件描述符集fdset指向,否则返回0。
- FD_SET(fd, &fdset): 位设置文件描述符fd文件描述符集fdset。
- FD_ZERO(&fdset): 初始化文件描述符集fdset所有文件描述符的零位。
这些宏的行为是不确定的,如果参数fd小于0或大于或等于FD_SETSIZE。
例如:
fd_set fds;
struct timeval tv;
/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;
/* tv now represents 1.5 seconds */
FD_ZERO(&fds);
/* adds sock to the file descriptor set */
FD_SET(sock, &fds);
/* wait 1.5 seconds for any data to be read
from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(sock, &fds))
{
recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
/* do something */
}
else
{
/* do something else */
}
Socket辅助函数 - Socket编程
本教程介绍socket编程时使用的所有辅助函数,及其他辅助函数的端口和服务,元网络字节顺序的教程。
write函数尝试写入n字节字节从缓冲区buf中相关的文件打开文件描述符,fildes指向。
也可以使用send()函数将数据发送到另一个进程。
#include <unistd.h>
int write(int fildes, const void *buf, int nbyte);
成功完成后,write()返回fildes的文件实际写入的字节数。这个数字是永远不会大于nbyte。否则,则返回-1
参数:
- fildes: 是socket函数返回一个套接字描述符。
- buf 要发送的数据是一个指针。
- nbyte 是要写入的字节数。如果nbyte是0,write()将返回0,如果该文件是一个普通文件,没有其他的结果,否则,结果是不确定的。
读函数试图打开文件描述符,fildes的相关的文件,到缓冲区buf指向读nbyte字节。
还可以使用的recv()函数来读取数据到另一个进程。
#include <unistd.h>
int read(int fildes, const void *buf, int nbyte);
成功完成后,write()返回fildes文件实际写入的字节数。这个数字是永远不会大于nbyte。否则,则返回-1。
参数:
- fildes: 是socket函数返回一个套接字描述符。
- buf 缓冲区读取信息..
- nbyte 是要读取的字节数。
fork函数创建一个新的进程。新进程称为子进程调用进程(父进程)的完全相同的副本。子进程继承父进程的许多属性。
#include <sys/types.h>
#include <unistd.h>
int fork(void);
成功完成后,fork()返回0到子进程和父进程返回子进程的进程ID。否则返回-1给父进程,没有子进程被创建并设置errno以指示错误。
参数:
- void: 不需要任何参数是必需。
bzero 函数的地方nbyte空字节的字符串s。这个函数将被用于设置具有空值的所有套接字结构。
void bzero(void *s, int nbyte);
此函数不返回任何东西(无返回值)。
参数:
- s: 指定字符串必须用空字节填充。这将是一个指向套接字的结构变量
- nbyte: 指定使用null值填充字节的数量。这将是套接字结构的大小。
bcmp 函数比较字节字符串s1的针对字节字符串s2。两个字符串都被假定为nbyte字节长。
int bcmp(const void *s1, const void *s2, int nbyte);
此功能如果两个字符串相同,则返回0,否则为1。 nbyte为0时bcmp()函数总是返回0 。
参数:
- s1: 指定要比较的第一个字符串。
- s2: 指定要比较的第二个字符串。
- nbyte: 指定的字节数进行比较。
bcopy 函数复制nbyte 个字节字符串s1到字符串s2。正确处理重叠的字符串。
void bcopy(const void *s1, void *s2, int nbyte);
此函数不返回任何值。
参数:
- s1: 指定的源字符串。
- s2: 指定目标字符串。
- nbyte: 指定要复制的字节数。
memset函数也可以用来设置结构变量,并以同样的方式作为 bzero.
void *memset(void *s, int c, int nbyte);
这个函数返回一个void指针,其实设定记忆体的指针,需要相应地释放。
参数:
- s: 指定源设置。
- c: 指定要设置的字符nbyte地方..
- nbyte: 指定的字节数进行设置。
Unix/Linux Socket 服务器代码实例 - Socket编程
我们已经看过关于服务器socket创建的教程,为了使进程TCP服务器端需要执行以下步骤:
- 创建一个socket使用socket() 系统调用.
- 使用bind()系统调用套接字绑定到一个地址。对于互联网上的服务器套接字,地址包括主机的端口号。
- 使用listen()系统调用连接监听。
- accept() 系统调用形式接受连接。此调用通常会阻塞,直到有客户端与服务器连接。
- 发送和接收数据,使用read() 和 write() 系统调用。
现在要把上面这些步骤的形式写成源代码。把这个代码写server.c文件并用gcc编译器编译。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main( int argc, char *argv[] )
{
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
{
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here process will
* go in sleep mode and will wait for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
/* Accept actual connection from the client */
newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr,
&clilen);
if (newsockfd < 0)
{
perror("ERROR on accept");
exit(1);
}
/* If connection is established then start communicating */
bzero(buffer,256);
n = read( newsockfd,buffer,255 );
if (n < 0)
{
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
/* Write a response to the client */
n = write(newsockfd,"I got your message",18);
if (n < 0)
{
perror("ERROR writing to socket");
exit(1);
}
return 0;
}
处理多个连接:
为了使服务器能够同时处理多个连接,我们在上面的代码进行以下更改:
- 将accept语句和下面的代码在一个无限循环。
- 建立连接后,调用fork()创建一个新的进程。
- 子进程将关闭sockfd中致电doprocessing函数,通过新的套接字文件描述符作为参数。当两个进程已经完成了他们对话中表示由doprocessing()返回,这个过程简单地退出。
- 父进程关闭newsockfd。因为所有这些代码是在一个无限循环,它接受语句将返回到等待下一个连接。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main( int argc, char *argv[] )
{
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
{
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here
* process will go in sleep mode and will wait
* for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
while (1)
{
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
{
perror("ERROR on accept");
exit(1);
}
/* Create child process */
pid = fork();
if (pid < 0)
{
perror("ERROR on fork");
exit(1);
}
if (pid == 0)
{
/* This is the client process */
close(sockfd);
doprocessing(newsockfd);
exit(0);
}
else
{
close(newsockfd);
}
} /* end of while */
}
Here is the simple implementation of doprocessing function.
void doprocessing (int sock)
{
int n;
char buffer[256];
bzero(buffer,256);
n = read(sock,buffer,255);
if (n < 0)
{
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
n = write(sock,"I got your message",18);
if (n < 0)
{
perror("ERROR writing to socket");
exit(1);
}
}
Unix/Linux Socket 客户端代码实例 - Socket编程
我们已经看过前面的教程,为了编写TCP客户端程序需要执行以下步骤:
- 创建一个套接字socket()系统调用。
- 套接字连接的服务器地址使用connect()系统调用。
- 发送和接收数据。做到这一点的方法有许多,但最简单的方法是使用read() 和write() 系统调用。
现在要把上面这些步骤的形式写成源代码。把这个代码写client.c文件并用gcc编译器编译。
运行该程序,并通过服务器的主机名和端口号连接到服务器,必须已经运行在另一个UNIX窗口。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
/* Create a socket point */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("ERROR opening socket");
exit(1);
}
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);
/* Now connect to the server */
if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
{
perror("ERROR connecting");
exit(1);
}
/* Now ask for a message from the user, this message
* will be read by server
*/
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
/* Send message to the server - by yiibai.com */
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
{
perror("ERROR writing to socket");
exit(1);
}
/* Now read server response */
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
{
perror("ERROR reading from socket");
exit(1);
}
printf("%s\n",buffer);
return 0;
}




