Unix Sockets 简明教程

Unix Socket - Quick Guide

What is a Socket?

套接字允许同一台或不同机器上的两个进程之间通信。说得更精确一些,套接字是一种使用标准 Unix 文件描述符与其他计算机通信的方式。在 Unix 中,每个 I/O 操作都是通过写入或读取文件描述符来完成的。文件描述符只是一个与打开的文件相关联的整数,它可以是网络连接、文本文件、终端或其他东西。

Sockets allow communication between two different processes on the same or different machines. To be more precise, it’s a way to talk to other computers using standard Unix file descriptors. In Unix, every I/O action is done by writing or reading a file descriptor. A file descriptor is just an integer associated with an open file and it can be a network connection, a text file, a terminal, or something else.

对于程序员来说,套接字的外观和行为与低级文件描述符非常相似。这是因为 read() 和 write() 等命令与套接字一起工作的原理与它们与文件和管道一起工作的原理相同。

To a programmer, a socket looks and behaves much like a low-level file descriptor. This is because commands such as read() and write() work with sockets in the same way they do with files and pipes.

套接字最早出现于 2.1BSD 中,随后在 4.2BSD 中被改进为当前形式。现在,大多数当前 UNIX 系统发行版都可以使用套接字功能。

Sockets were first introduced in 2.1BSD and subsequently refined into their current form with 4.2BSD. The sockets feature is now available with most current UNIX system releases.

Where is Socket Used?

Unix 套接字用于客户端-服务器应用程序框架。服务器是一个根据客户端请求执行某些功能的进程。大多数应用程序级协议(如 FTP、SMTP 和 POP3)都使用套接字在客户端和服务器之间建立连接,然后交换数据。

A Unix Socket is used in a client-server application framework. A server is a process that performs some functions on request from a client. Most of the application-level protocols like FTP, SMTP, and POP3 make use of sockets to establish connection between client and server and then for exchanging data.

Socket Types

用户可以使用四种类型的套接字。前两种最常用,后两种很少用。

There are four types of sockets available to the users. The first two are most commonly used and the last two are rarely used.

通常认为进程只能在同类型套接字之间进行通信,但是没有规定阻止不同类型套接字之间的通信。

Processes are presumed to communicate only between sockets of the same type but there is no restriction that prevents communication between sockets of different types.

  1. Stream Sockets − Delivery in a networked environment is guaranteed. If you send through the stream socket three items "A, B, C", they will arrive in the same order − "A, B, C". These sockets use TCP (Transmission Control Protocol) for data transmission. If delivery is impossible, the sender receives an error indicator. Data records do not have any boundaries.

  2. Datagram Sockets − Delivery in a networked environment is not guaranteed. They’re connectionless because you don’t need to have an open connection as in Stream Sockets − you build a packet with the destination information and send it out. They use UDP (User Datagram Protocol).

  3. Raw Sockets − These provide users access to the underlying communication protocols, which support socket abstractions. These sockets are normally datagram oriented, though their exact characteristics are dependent on the interface provided by the protocol. Raw sockets are not intended for the general user; they have been provided mainly for those interested in developing new communication protocols, or for gaining access to some of the more cryptic facilities of an existing protocol.

  4. Sequenced Packet Sockets − They are similar to a stream socket, with the exception that record boundaries are preserved. This interface is provided only as a part of the Network Systems (NS) socket abstraction, and is very important in most serious NS applications. Sequenced-packet sockets allow the user to manipulate the Sequence Packet Protocol (SPP) or Internet Datagram Protocol (IDP) headers on a packet or a group of packets, either by writing a prototype header along with whatever data is to be sent, or by specifying a default header to be used with all outgoing data, and allows the user to receive the headers on incoming packets.

What is Next?

接下来的几章旨在加强你的基础知识,在你使用套接字编写服务器和客户端程序之前奠定基础。如果您想直接跳到如何编写客户端和服务器程序,您可以这样做,但这不是推荐做法。强烈建议你一步一步地进行,完成这些最初的几章,在你开始编程之前打好基础。

The next few chapters are meant to strengthen your basics and prepare a foundation before you can write Server and Client programs using socket. If you directly want to jump to see how to write a client and server program, then you can do so but it is not recommended. It is strongly recommended that you go step by step and complete these initial few chapters to make your base before moving on to do programming.

Unix Socket - Network Addresses

在我们继续进行实际操作之前,让我们简要讨论一下网络地址 − IP 地址。

Before we proceed with the actual stuff, let us discuss a bit about the Network Addresses − the IP Address.

IP 主机地址,或更通俗的说 IP 地址,用于识别连接到互联网的主机。IP 代表互联网协议,是指互联网的整体网络架构中的互联网层。

The IP host address, or more commonly just IP address, is used to identify hosts connected to the Internet. IP stands for Internet Protocol and refers to the Internet Layer of the overall network architecture of the Internet.

IP 地址是一个 32 位量,被解释为四个 8 位数或八位字节。每个 IP 地址唯一标识参与用户网络、网络上的主机以及用户网络的类。

An IP address is a 32-bit quantity interpreted as four 8-bit numbers or octets. Each IP address uniquely identifies the participating user network, the host on the network, and the class of the user network.

IP 地址通常以点分十进制表示法 N1.N2.N3.N4 编写,其中每个 Ni 都是一个介于 0 到 255 的十进制数字(00 到 FF 十六进制)。

An IP address is usually written in a dotted-decimal notation of the form N1.N2.N3.N4, where each Ni is a decimal number between 0 and 255 decimal (00 through FF hexadecimal).

Address Classes

IP 地址由互联网号码分配局 (IANA) 管理和创建。有五种不同的地址类。您可以通过检查 IP 地址的前四个位来确定 IP 地址属于哪个类。

IP addresses are managed and created by the Internet Assigned Numbers Authority (IANA). There are five different address classes. You can determine which class an IP address is in by examining the first four bits of the IP address.

  1. Class A addresses begin with 0xxx, or 1 to 126 decimal.

  2. Class B addresses begin with 10xx, or 128 to 191 decimal.

  3. Class C addresses begin with 110x, or 192 to 223 decimal.

  4. Class D addresses begin with 1110, or 224 to 239 decimal.

  5. Class E addresses begin with 1111, or 240 to 254 decimal.

01111111127 十进制开头的地址保留用于环回和本地计算机的内部测试 [您可以测试这一点:您应该始终能够 ping 127.0.0.1 ,它指向您自己];D 类地址保留用于多播;E 类地址保留供将来使用。不应将其用于主机地址。

Addresses beginning with 01111111, or 127 decimal, are reserved for loopback and for internal testing on a local machine [You can test this: you should always be able to ping 127.0.0.1, which points to yourself]; Class D addresses are reserved for multicasting; Class E addresses are reserved for future use. They should not be used for host addresses.

Example

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

Subnetting

子网划分为或子网划定基本上意味着分支网络。出于各种原因可以进行此操作,例如组织中的网络、使用不同的物理媒体(例如以太网、FDDI、WAN 等)、保留地址空间和安全性。最常见的原因是控制网络流量。

Subnetting or subnetworking basically means to branch off a network. It can be done for a variety of reasons like network in an organization, use of different physical media (such as Ethernet, FDDI, WAN, etc.), preservation of address space, and security. The most common reason is to control network traffic.

子网划分的基本思路是将 IP 地址的主机标识符部分划分为两部分——

The basic idea in subnetting is to partition the host identifier portion of the IP address into two parts −

  1. A subnet address within the network address itself; and

  2. A host address on the subnet.

例如,常见的 B 类地址格式为 N1.N2.S.H,其中 N1.N2 标识 B 类网络,8 位 S 字段标识子网,8 位 H 字段标识子网上的主机。

For example, a common Class B address format is N1.N2.S.H, where N1.N2 identifies the Class B network, the 8-bit S field identifies the subnet, and the 8-bit H field identifies the host on the subnet.

Unix Socket - Network Host Names

就数字而言,主机名很难记住,因此它们被称为普通名称,例如 Takshila 或 Nalanda。我们编写软件应用程序来查找与给定名称对应的点分 IP 地址。

Host names in terms of numbers are difficult to remember and hence they are termed by ordinary names such as Takshila or Nalanda. We write software applications to find out the dotted IP address corresponding to a given name.

根据给定的字母数字主机名找出点分 IP 地址的过程称为 hostname resolution

The process of finding out dotted IP address based on the given alphanumeric host name is known as hostname resolution.

由驻留在高容量系统上的特殊软件执行主机名解析。这些系统称为域名系统 (DNS),它保留 IP 地址和相应的普通名称的映射。

A hostname resolution is done by special software residing on high-capacity systems. These systems are called Domain Name Systems (DNS), which keep the mapping of IP addresses and the corresponding ordinary names.

The /etc/hosts File

主机名与 IP 地址之间的对应关系保存在一个名为 hosts 的文件中。在大多数系统上,此文件位于 /etc 目录中。

The correspondence between host names and IP addresses is maintained in a file called hosts. On most of the systems, this file is found in /etc directory.

此文件中的条目看起来如下——

Entries in this file look like the following −

# 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 地址到主机名和反之亦然时,将使用此文件。

Note that more than one name may be associated with a given IP address. This file is used while converting from IP address to host name and vice versa.

您无法访问此文件进行编辑,因此,如果您想将任何主机名与 IP 地址一起放入,那么您需要具有 root 权限。

You would not have access to edit this file, so if you want to put any host name along with IP address, then you would need to have root permission.

Unix Socket - Client Server Model

大多数网络应用程序都使用客户端-服务器架构,它表示两个进程或两个应用程序彼此通信以交换一些信息。两个进程之一充当客户端进程,另一个进程充当服务器。

Most of the Net Applications use the Client-Server architecture, which refers to two processes or two applications that communicate with each other to exchange some information. One of the two processes acts as a client process, and another process acts as a server.

Client Process

这是该进程,它通常会请求信息。收到响应后,该进程可能会终止或可能会执行一些其他处理。

This is the process, which typically makes a request for information. After getting the response, this process may terminate or may do some other processing.

Example ,互联网浏览器作为客户端应用程序运行,它向 Web 服务器发送请求以获取一个 HTML 网页。

Example, Internet Browser works as a client application, which sends a request to the Web Server to get one HTML webpage.

Server Process

这是从客户端获取请求的进程。从客户端获取请求后,此进程将执行必需的处理,收集请求的信息并将其发送给请求方客户端。完成后,它将准备好为另一个客户端提供服务。服务器进程始终处于警报状态并随时准备服务传入请求。

This is the process which takes a request from the clients. After getting a request from the client, this process will perform the required processing, gather the requested information, and send it to the requestor client. Once done, it becomes ready to serve another client. Server processes are always alert and ready to serve incoming requests.

Example - Web 服务器不断等待互联网浏览器的请求,并且一旦它收到来自浏览器的任何请求,它就会挑选请求的 HTML 页面并将其发回该浏览器。

Example − Web Server keeps waiting for requests from Internet Browsers and as soon as it gets any request from a browser, it picks up a requested HTML page and sends it back to that Browser.

注意,客户端需要知道服务器的地址,但是服务器不需要知道客户端的地址甚至存在,直到建立连接后。建立连接后,双方都可以发送和接收信息。

Note that the client needs to know the address of the server, but the server does not need to know the address or even the existence of the client prior to the connection being established. Once a connection is established, both sides can send and receive information.

2-tier and 3-tier architectures

有两种类型的客户端-服务器架构 −

There are two types of client-server architectures −

  1. 2-tier architecture − In this architecture, the client directly interacts with the server. This type of architecture may have some security holes and performance problems. Internet Explorer and Web Server work on two-tier architecture. Here security problems are resolved using Secure Socket Layer (SSL).

  2. 3-tier architectures − In this architecture, one more software sits in between the client and the server. This middle software is called ‘middleware’. Middleware are used to perform all the security checks and load balancing in case of heavy load. A middleware takes all requests from the client and after performing the required authentication, it passes that request to the server. Then the server does the required processing and sends the response back to the middleware and finally the middleware passes this response back to the client. If you want to implement a 3-tier architecture, then you can keep any middleware like Web Logic or WebSphere software in between your Web Server and Web Browser.

Types of Server

您可以有两种类型的服务器 −

There are two types of servers you can have −

  1. Iterative Server − This is the simplest form of server where a server process serves one client and after completing the first request, it takes request from another client. Meanwhile, another client keeps waiting.

  2. Concurrent Servers − This type of server runs multiple concurrent processes to serve many requests at a time because one process may take longer and another client cannot wait for so long. The simplest way to write a concurrent server under Unix is to fork a child process to handle each client separately.

How to Make Client

用于建立连接的系统调用对于客户端和服务器来说是不同的,但两者都涉及套接字的基本结构。这两个进程都建立自己的套接字。

The system calls for establishing a connection are somewhat different for the client and the server, but both involve the basic construct of a socket. Both the processes establish their own sockets.

在客户端建立套接字涉及以下步骤 −

The steps involved in establishing a socket on the client side are as follows −

  1. Create a socket with the socket() system call.

  2. Connect the socket to the address of the server using the connect() system call.

  3. Send and receive data. There are a number of ways to do this, but the simplest way is to use the read() and write() system calls.

How to make a Server

在服务器端建立套接字涉及以下步骤 −

The steps involved in establishing a socket on the server side are as follows −

  1. Create a socket with the socket() system call.

  2. Bind the socket to an address using the bind() system call. For a server socket on the Internet, an address consists of a port number on the host machine.

  3. Listen for connections with the listen() system call.

  4. Accept a connection with the accept() system call. This call typically blocks the connection until a client connects with the server.

  5. Send and receive data using the read() and write() system calls.

Client and Server Interaction

以下是显示完整客户端和服务器交互的图表 −

Following is the diagram showing the complete Client and Server interaction −

socket client server

Unix Socket - Structures

Unix 套接字编程中使用各种结构来保存有关地址和端口等信息的的信息。大多数套接字函数需要一个指向套接字地址结构的指针作为参数。本章定义的结构与 Internet 协议族相关。

Various structures are used in Unix Socket Programming to hold information about the address and port, and other information. Most socket functions require a pointer to a socket address structure as an argument. Structures defined in this chapter are related to Internet Protocol Family.

sockaddr

第一个结构是 sockaddr,它保存套接字信息——

The first structure is sockaddr that holds the socket information −

struct sockaddr {
   unsigned short   sa_family;
   char             sa_data[14];
};

这是一个通用的套接字地址结构,它将在大多数套接字函数调用中传递。下表提供了成员字段的说明——

This is a generic socket address structure, which will be passed in most of the socket function calls. The following table provides a description of the member fields −

Attribute

Values

Description

sa_family

AF_INET AF_UNIX AF_NS AF_IMPLINK

It 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 by sockaddr_in structure defined below.

sockaddr in

帮助你引用套接字元素的第二个结构如下所示 −

The second structure that helps you to reference to the socket’s elements is as follows −

struct sockaddr_in {
   short int            sin_family;
   unsigned short int   sin_port;
   struct in_addr       sin_addr;
   unsigned char        sin_zero[8];
};

下面是成员字段的说明 −

Here is the description of the member fields −

Attribute

Values

Description

sa_family

AF_INET AF_UNIX AF_NS AF_IMPLINK

It 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.

in addr

此结构仅用作以上结构中的结构字段,并保存 32 位网络 ID/主机 ID。

This structure is used only in the above structure as a structure field and holds 32 bit netid/hostid.

struct in_addr {
   unsigned long s_addr;
};

下面是成员字段的说明 −

Here is the description of the member fields −

Attribute

Values

Description

s_addr

service port

A 32-bit IP address in Network Byte Order.

hostent

此结构用于保存与主机相关的的信息。

This structure is used to keep information related to host.

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]
};

下面是成员字段的说明 −

Here is the description of the member fields −

Attribute

Values

Description

h_name

ti.com etc.

It is the official name of the host. For example, tutorialspoint.com, google.com, etc.

h_aliases

TI

It holds a list of host name aliases.

h_addrtype

AF_INET

It contains the address family and in case of Internet based application, it will always be AF_INET.

h_length

4

It holds the length of the IP address, which is 4 for Internet Address.

h_addr_list

in_addr

For Internet addresses, the array of pointers h_addr_list[0], h_addr_list[1], and so on, are points to structure in_addr.

NOTE − 为保持向后兼容性,h_addr 定义为 h_addr_list[0]。

NOTE − h_addr is defined as h_addr_list[0] to keep backward compatibility.

servent

此特定结构用于保存与服务和相关端口有关的信息。

This particular structure is used to keep information related to service and associated ports.

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
};

下面是成员字段的说明 −

Here is the description of the member fields −

Attribute

Values

Description

s_name

http

This is the official name of the service. For example, SMTP, FTP POP3, etc.

s_aliases

ALIAS

It holds the list of service aliases. Most of the time this will be set to NULL.

s_port

80

It will have associated port number. For example, for HTTP, this will be 80.

s_proto

TCP UDP

It is set to the protocol used. Internet services are provided using either TCP or UDP.

Tips on Socket Structures

套接字地址结构是每个网络程序的组成部分。我们对其进行分配、填写,并将其指针传递给各种套接字函数。有时我们会向套接字函数传递指向其中一个结构的指针,它会填写内容。

Socket address structures are an integral part of every network program. We allocate them, fill them in, and pass pointers to them to various socket functions. Sometimes we pass a pointer to one of these structures to a socket function and it fills in the contents.

我们总是通过引用方式传递这些结构(即,我们传递一个指向该结构的指针,而不是结构本身),并且我们总是将结构的大小作为一个其他参数传递。

We always pass these structures by reference (i.e., we pass a pointer to the structure, not the structure itself), and we always pass the size of the structure as another argument.

当套接字函数填写一个结构时,长度也会通过引用进行传递,以便函数可以更新其值。我们将这些值称为值结果参数。

When a socket function fills in a structure, the length is also passed by reference, so that its value can be updated by the function. We call these value-result arguments.

始终使用 memset()(对于 bzero() 函数)将结构变量设置为 NULL(即 '\0'),否则您的结构中可能会出现意外的垃圾值。

Always, set the structure variables to NULL (i.e., '\0') by using memset() for bzero() functions, otherwise it may get unexpected junk values in your structure.

Unix Socket - Ports and Services

当客户端进程想连接服务器时,客户端必须有一种方法来识别它想要连接的服务器。如果客户端知道驻留服务器的主机的 32 位互联网地址,它就可以联系该主机。但是客户端如何识别该主机上正在运行的特定服务器进程?

When a client process wants to a connect a server, the client must have a way of identifying the server that it wants to connect. If the client knows the 32-bit Internet address of the host on which the server resides, it can contact that host. But how does the client identify the particular server process running on that host?

为了解决在主机上识别特定服务器进程的问题,TCP 和 UDP 都定义了一组众所周知端口。

To resolve the problem of identifying a particular server process running on a host, both TCP and UDP have defined a group of well-known ports.

就我们的目的而言,端口将被定义为 1024 到 65535 之间的整数。这是因为小于 1024 的所有端口号都被认为是众所周知的——例如,Telnet 使用端口 23,http 使用 80,ftp 使用 21,依此类推。

For our purpose, a port will be defined as an integer number between 1024 and 65535. This is because all port numbers smaller than 1024 are considered well-known — for example, telnet uses port 23, http uses 80, ftp uses 21, and so on.

网络服务的端口分配可以在文件 /etc/services 中找到。如果您要编写自己的服务器,则必须小心为服务器分配一个端口。您应该确保此端口未分配给任何其他服务器。

The port assignments to network services can be found in the file /etc/services. If you are writing your own server then care must be taken to assign a port to your server. You should make sure that this port should not be assigned to any other server.

通常做法是分配大于 5000 的任何端口号。但有许多组织编写的服务器具有大于 5000 的端口号。例如,Yahoo Messenger 在 5050 上运行,SIP 服务器在 5060 上运行,等等。

Normally it is a practice to assign any port number more than 5000. But there are many organizations who have written servers having port numbers more than 5000. For example, Yahoo Messenger runs on 5050, SIP Server runs on 5060, etc.

Example Ports and Services

以下是一份服务和关联端口的小列表。您可以在 IANA - TCP/IP Port Assignments 找到最更新的互联网端口和关联服务列表。

Here is a small list of services and associated ports. You can find the most updated list of internet ports and associated service at IANA - TCP/IP Port Assignments.

Service

Port Number

Service Description

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).

Port and Service Functions

Unix 提供下列函数用于从 /etc/services 文件中获取服务名称。

Unix provides the following functions to fetch service name from the /etc/services file.

  1. struct servent *getservbyname(char *name, char *proto) − This call takes service name and protocol name, and returns the corresponding port number for that service.

  2. struct servent *getservbyport(int port, char *proto) − This call takes port number and protocol name, and returns the corresponding service name.

每个函数的返回值都是一个指向具有下列形式的结构的指针 −

The return value for each function is a pointer to a structure with the following form −

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
};

下面是成员字段的说明 −

Here is the description of the member fields −

Attribute

Values

Description

s_name

http

It is the official name of the service. For example, SMTP, FTP POP3, etc.

s_aliases

ALIAS

It holds the list of service aliases. Most of the time, it will be set to NULL.

s_port

80

It will have the associated port number. For example, for HTTP, it will be 80.

s_proto

TCP UDP

It is set to the protocol used. Internet services are provided using either TCP or UDP.

Unix Socket - Network Byte Orders

不幸的是,并非所有计算机都以相同的顺序存储构成多字节值的那个字节。考虑一个由 2 个字节组成的 16 位互联网。有两种方法来存储此值。

Unfortunately, not all computers store the bytes that comprise a multibyte value in the same order. Consider a 16-bit internet that is made up of 2 bytes. There are two ways to store this value.

  1. Little Endian − In this scheme, low-order byte is stored on the starting address (A) and high-order byte is stored on the next address (A + 1).

  2. Big Endian − In this scheme, high-order byte is stored on the starting address (A) and low-order byte is stored on the next address (A + 1).

为了允许具有不同字节顺序约定的机器互相通信,因特网协议为通过网络传输的数据指定了一个规范的字节顺序约定。这被称为网络字节顺序。

To allow machines with different byte order conventions communicate with each other, the Internet protocols specify a canonical byte order convention for data transmitted over the network. This is known as Network Byte Order.

在建立 Internet 套接字连接时,您必须确保 sockaddr_in 结构的 sin_port 和 sin_addr 成员中的数据以网络字节顺序表示。

While establishing an Internet socket connection, you must make sure that the data in the sin_port and sin_addr members of the sockaddr_in structure are represented in Network Byte Order.

Byte Ordering Functions

用于在主机内部表示和网络字节顺序之间转换数据的例程如下 -

Routines for converting data between a host’s internal representation and Network Byte Order are as follows −

Function

Description

htons()

Host to Network Short

htonl()

Host to Network Long

ntohl()

Network to Host Long

ntohs()

Network to Host Short

下面列出了一些有关这些函数的更多详细信息 −

Listed below are some more detail about these functions −

  1. unsigned short htons(unsigned short hostshort) − This function converts 16-bit (2-byte) quantities from host byte order to network byte order.

  2. unsigned long htonl(unsigned long hostlong) − This function converts 32-bit (4-byte) quantities from host byte order to network byte order.

  3. unsigned short ntohs(unsigned short netshort) − This function converts 16-bit (2-byte) quantities from network byte order to host byte order.

  4. unsigned long ntohl(unsigned long netlong) − This function converts 32-bit quantities from network byte order to host byte order.

这些函数是宏,并导致将转换源代码插入到调用程序中。在小端机器上,代码会将值更改为网络字节顺序。在大型机器上,不插入任何代码,因为不需要这些代码;这些函数被定义为 null。

These functions are macros and result in the insertion of conversion source code into the calling program. On little-endian machines, the code will change the values around to network byte order. On big-endian machines, no code is inserted since none is needed; the functions are defined as null.

Program to Determine Host Byte Order

将以下代码保存在一个名为 byteorder.c 的文件中,然后编译它并在机器上运行。

Keep the following code in a file byteorder.c and then compile it and run it over your machine.

在此示例中,我们将两个字节值 0x0102 存储在 short integer 中,然后查看两个连续的字节 c[0](地址 A)和 c[1](地址 A + 1)以确定字节顺序。

In this example, we store the two-byte value 0x0102 in the short integer and then look at the two consecutive bytes, c[0] (the address A) and c[1] (the address A + 1) to determine the byte order.

#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);
}

此程序在奔腾机器上生成的输出如下 -

An output generated by this program on a Pentium machine is as follows −

$> gcc byteorder.c
$> ./a.out
little-endian
$>

Unix Socket - IP Address Functions

Unix 提供了各种函数调用,可以帮助你处理 IP 地址。这些函数在 ASCII 字符串(人类更喜欢使用)和网络字节序二进制值(存储在套接字地址结构中的值)之间转换互联网地址。

Unix provides various function calls to help you manipulate IP addresses. These functions convert Internet addresses between ASCII strings (what humans prefer to use) and network byte ordered binary values (values that are stored in socket address structures).

以下三个函数调用用于 IPv4 寻址:

The following three function calls are used for IPv4 addressing −

  1. int inet_aton(const char *strptr, struct in_addr *addrptr)

  2. in_addr_t inet_addr(const char *strptr)

  3. char *inet_ntoa(struct in_addr inaddr)

int inet_aton(const char *strptr, struct in_addr *addrptr)

此函数调用将互联网标准点分十进制表示法中的指定字符串转换为网络地址,并将地址存储在所提供的结构中。转换后的地址将采用网络字节序(从左到右排序字节)。如果字符串有效则返回 1,如果出错则返回 0。

This function call converts the specified string in the Internet standard dot notation to a network address, and stores the address in the structure provided. The converted address will be in Network Byte Order (bytes ordered from left to right). It returns 1 if the string was valid and 0 on error.

以下是在使用示例:

Following is the usage example −

#include <arpa/inet.h>

(...)

   int retval;
   struct in_addr addrptr

   memset(&addrptr, '\0', sizeof(addrptr));
   retval = inet_aton("68.178.157.132", &addrptr);

(...)

in_addr_t inet_addr(const char *strptr)

此函数调用将互联网标准点分十进制表示法中的指定字符串转换为适合用作互联网地址的整数值。转换后的地址将采用网络字节序(从左到右排序字节)。它返回一个 32 位二进制网络字节序 IPv4 地址,如果出错,则返回 INADDR_NONE。

This function call converts the specified string in the Internet standard dot notation to an integer value suitable for use as an Internet address. The converted address will be in Network Byte Order (bytes ordered from left to right). It returns a 32-bit binary network byte ordered IPv4 address and INADDR_NONE on error.

以下是在使用示例:

Following is the usage example −

#include <arpa/inet.h>

(...)

   struct sockaddr_in dest;

   memset(&dest, '\0', sizeof(dest));
   dest.sin_addr.s_addr = inet_addr("68.178.157.132");

(...)

char *inet_ntoa(struct in_addr inaddr)

此函数调用将指定的互联网主机地址转换为互联网标准点分十进制表示法的字符串。

This function call converts the specified Internet host address to a string in the Internet standard dot notation.

以下是在使用示例:

Following is the usage example −

#include <arpa/inet.h>

(...)

   char *ip;

   ip = inet_ntoa(dest.sin_addr);

   printf("IP Address is: %s\n",ip);

(...)

Unix Socket - Core Functions

本章介绍了编写完整的 TCP 客户端和服务器所需的核心套接字函数。

This chapter describes the core socket functions required to write a complete TCP client and server.

下图显示了完整的客户端和服务器交互 -

The following diagram shows the complete Client and Server interaction −

socket client server

The socket Function

为了执行网络 I/O,进程必须做的第一件事是调用套接字函数,指定所需的通信协议类型和协议族等。

To perform network I/O, the first thing a process must do is, call the socket function, specifying the type of communication protocol desired and protocol family, etc.

#include <sys/types.h>
#include <sys/socket.h>

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

此调用会返回一个套接字描述符,可以在后续的系统调用中使用,或在出错时返回 -1。

This call returns a socket descriptor that you can use in later system calls or -1 on error.

Parameters

family − 指定协议系列,是下面所示的常量之一 −

family − It specifies the protocol family and is one of the constants shown below −

Family

Description

AF_INET

IPv4 protocols

AF_INET6

IPv6 protocols

AF_LOCAL

Unix domain protocols

AF_ROUTE

Routing Sockets

AF_KEY

Ket socket

本章不涉及 IPv4 以外的其他协议。

This chapter does not cover other protocols except IPv4.

type − 指定你想要哪种套接字。可以采用以下值之一 −

type − It specifies the kind of socket you want. It can take one of the following values −

Type

Description

SOCK_STREAM

Stream socket

SOCK_DGRAM

Datagram socket

SOCK_SEQPACKET

Sequenced packet socket

SOCK_RAW

Raw socket

protocol − 应该将参数设置为下面给出的特定协议类型,或设置为 0 以选择系统针对给定的系列和类型的默认项 −

protocol − The argument should be set to the specific protocol type given below, or 0 to select the system’s default for the given combination of family and type −

Protocol

Description

IPPROTO_TCP

TCP transport protocol

IPPROTO_UDP

UDP transport protocol

IPPROTO_SCTP

SCTP transport protocol

The connect Function

connect 函数由 TCP 客户端用于建立与 TCP 服务器的连接。

The connect function is used by a TCP client to establish a connection with a TCP server.

#include <sys/types.h>
#include <sys/socket.h>

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

如果成功连接到服务器,此调用会返回 0,否则在出错时会返回 -1。

This call returns 0 if it successfully connects to the server, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. serv_addr − It is a pointer to struct sockaddr that contains destination IP address and port.

  3. addrlen − Set it to sizeof(struct sockaddr).

The bind Function

bind 函数将本地协议地址分配给套接字。使用 Internet 协议时,该协议地址由一个 32 位 IPv4 地址或一个 128 位 IPv6 地址,以及一个 16 位 TCP 或 UDP 端口号相结合构成。该函数仅由 TCP 服务器调用。

The bind function assigns a local protocol address to a socket. With the Internet protocols, the protocol address is the combination of either a 32-bit IPv4 address or a 128-bit IPv6 address, along with a 16-bit TCP or UDP port number. This function is called by TCP server only.

#include <sys/types.h>
#include <sys/socket.h>

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

如果成功绑定到该地址,此调用会返回 0,否则在出错时会返回 -1。

This call returns 0 if it successfully binds to the address, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. my_addr − It is a pointer to struct sockaddr that contains the local IP address and port.

  3. addrlen − Set it to sizeof(struct sockaddr).

你可以自动放入你的 IP 地址和端口

You can put your IP address and your port automatically

端口号的 0 值表示系统将选择一个随机端口,并且 IP 地址的 INADDR_ANY 值表示将自动分配服务器的 IP 地址。

A 0 value for port number means that the system will choose a random port, and INADDR_ANY value for IP address means the server’s IP address will be assigned automatically.

server.sin_port = 0;
server.sin_addr.s_addr = INADDR_ANY;

NOTE − 低于 1024 的所有端口都是保留的。你可以设置 1024 以上和 65535 以下的端口,除非它们被其他程序使用。

NOTE − All ports below 1024 are reserved. You can set a port above 1024 and below 65535 unless they are the ones being used by other programs.

The listen Function

listen 函数仅由 TCP 服务器调用,并且它执行两个操作 −

The listen function is called only by a TCP server and it performs two actions −

  1. The listen function converts an unconnected socket into a passive socket, indicating that the kernel should accept incoming connection requests directed to this socket.

  2. The second argument to this function specifies the maximum number of connections the kernel should queue for this socket.

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd,int backlog);

此调用成功时返回 0,否则在错误时返回 -1。

This call returns 0 on success, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. backlog − It is the number of allowed connections.

The accept Function

accept 函数由 TCP 服务器调用,以从已完成连接队列最前端返回下一个已完成连接。调用的签名如下:

The accept function is called by a TCP server to return the next completed connection from the front of the completed connection queue. The signature of the call is as follows −

#include <sys/types.h>
#include <sys/socket.h>

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

此调用成功时返回非负描述符,否则在错误时返回 -1。假定返回的描述符为客户端套接字描述符,并且所有读写操作都将在此描述符上执行以与客户端进行通信。

This call returns a non-negative descriptor on success, otherwise it returns -1 on error. The returned descriptor is assumed to be a client socket descriptor and all read-write operations will be done on this descriptor to communicate with the client.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. cliaddr − It is a pointer to struct sockaddr that contains client IP address and port.

  3. addrlen − Set it to sizeof(struct sockaddr).

The send Function

send 函数用于通过流套接字或已连接的数据报套接字发送数据。如果您想通过未连接的数据报套接字发送数据,则必须使用 sendto() 函数。

The send function is used to send data over stream sockets or CONNECTED datagram sockets. If you want to send data over UNCONNECTED datagram sockets, you must use sendto() function.

您可以使用 write() 系统调用来发送数据。其签名如下:

You can use write() system call to send data. Its signature is as follows −

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

此调用返回发送的字节数,否则在错误时返回 -1。

This call returns the number of bytes sent out, otherwise it will return -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. msg − It is a pointer to the data you want to send.

  3. len − It is the length of the data you want to send (in bytes).

  4. flags − It is set to 0.

The recv Function

recv 函数用于通过流套接字或已连接的数据报套接字接收数据。如果您想通过未连接的数据报套接字接收数据,则必须使用 recvfrom()。

The recv function is used to receive data over stream sockets or CONNECTED datagram sockets. If you want to receive data over UNCONNECTED datagram sockets you must use recvfrom().

您可以使用 read() 系统调用来读取数据。此调用在帮助程序函数章节中进行了解释。

You can use read() system call to read the data. This call is explained in helper functions chapter.

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

此调用返回读入缓冲区的字节数,否则在错误时返回 -1。

This call returns the number of bytes read into the buffer, otherwise it will return -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. buf − It is the buffer to read the information into.

  3. len − It is the maximum length of the buffer.

  4. flags − It is set to 0.

The sendto Function

sendto 函数用于通过未连接的数据报套接字发送数据。其签名如下:

The sendto function is used to send data over UNCONNECTED datagram sockets. Its signature is as follows −

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

此调用返回发送的字节数,否则在错误时返回 -1。

This call returns the number of bytes sent, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. msg − It is a pointer to the data you want to send.

  3. len − It is the length of the data you want to send (in bytes).

  4. flags − It is set to 0.

  5. to − It is a pointer to struct sockaddr for the host where data has to be sent.

  6. tolen − It is set it to sizeof(struct sockaddr).

The recvfrom Function

recvfrom 函数用来从未连接的数据报套接字接收数据。

The recvfrom function is used to receive data from UNCONNECTED datagram sockets.

int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);

此调用返回读取到缓冲区中的字节数,否则在出错时返回 -1。

This call returns the number of bytes read into the buffer, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. buf − It is the buffer to read the information into.

  3. len − It is the maximum length of the buffer.

  4. flags − It is set to 0.

  5. from − It is a pointer to struct sockaddr for the host where data has to be read.

  6. fromlen − It is set it to sizeof(struct sockaddr).

The close Function

close 函数用来关闭客户端和服务器之间的通信。其语法如下:

The close function is used to close the communication between the client and the server. Its syntax is as follows −

int close( int sockfd );

此调用成功时返回 0,否则在错误时返回 -1。

This call returns 0 on success, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

The shutdown Function

shutdown 函数用来优雅地关闭客户端和服务器之间的通信。与 close 函数相比,此函数提供更多控制。以下是 shutdown 的语法:

The shutdown function is used to gracefully close the communication between the client and the server. This function gives more control in comparison to the close function. Given below is the syntax of shutdown −

int shutdown(int sockfd, int how);

此调用成功时返回 0,否则在错误时返回 -1。

This call returns 0 on success, otherwise it returns -1 on error.

Parameters

  1. sockfd − It is a socket descriptor returned by the socket function.

  2. how − Put one of the numbers − 0 − indicates that receiving is not allowed, 1 − indicates that sending is not allowed, and 2 − indicates that both sending and receiving are not allowed. When how is set to 2, it’s the same thing as close().

The select Function

select 函数指示指定的文件描述符中的哪一个已准备好进行读取、准备好进行写入,或有待处理的错误条件。

The select function indicates which of the specified file descriptors is ready for reading, ready for writing, or has an error condition pending.

当一个应用程序调用 recv 或 recvfrom 时,它会被阻塞,直到针对该套接字到达数据。当输入数据流为空时,一个应用程序可以执行其他有用的处理。另一种情况是当一个应用程序从多个套接字接收数据时。

When an application calls recv or recvfrom, it is blocked until data arrives for that socket. An application could be doing other useful processing while the incoming data stream is empty. Another situation is when an application receives data from multiple sockets.

在一个输入队列中没有数据的套接字上调用 recv 或 recvfrom 会阻止立即从其他套接字接收数据。select 函数调用通过允许程序轮询所有套接字句柄来解决此问题,以查看它们是否可用于非阻塞读取和写入操作。

Calling recv or recvfrom on a socket that has no data in its input queue prevents immediate reception of data from other sockets. The select function call solves this problem by allowing the program to poll all the socket handles to see if they are available for non-blocking reading and writing operations.

以下是 select 的语法:

Given below is the syntax of select −

 int select(int  nfds, fd_set  *readfds, fd_set  *writefds, fd_set *errorfds, struct timeval *timeout);

此调用成功时返回 0,否则在错误时返回 -1。

This call returns 0 on success, otherwise it returns -1 on error.

Parameters

  1. nfds − It specifies the range of file descriptors to be tested. The select() function tests file descriptors in the range of 0 to nfds-1

  2. readfds − It 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. It can be NULL to indicate an empty set.

  3. writefds − It 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. It can be NULL to indicate an empty set.

  4. exceptfds − It 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. It can be NULL to indicate an empty set.

  5. timeout − It points 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.

来自 select 的返回值是文件描述符集中指定的文件句柄数,这些句柄已准备好进行 I/O。如果 timeout 字段指定的时间限制达到,select 返回 0。以下宏用于操作文件描述符集:

The return value from select is the number of handles specified in the file descriptor sets that are ready for I/O. If the time limit specified by the timeout field is reached, select return 0. The following macros exist for manipulating a file descriptor set −

  1. FD_CLR(fd, &fdset) − Clears the bit for the file descriptor fd in the file descriptor set fdset.

  2. FD_ISSET(fd, &fdset) − Returns a non-zero value if the bit for the file descriptor fd is set in the file descriptor set pointed to by fdset, and 0 otherwise.

  3. FD_SET(fd, &fdset) − Sets the bit for the file descriptor fd in the file descriptor set fdset.

  4. FD_ZERO(&fdset) − Initializes the file descriptor set fdset to have zero bits for all file descriptors.

如果 fd 参数小于 0 或大于或等于 FD_SETSIZE,则这些宏的行为是未定义的。

The behavior of these macros is undefined if the fd argument is less than 0 or greater than or equal to FD_SETSIZE.

Example

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 */
}

Unix Socket - Helper Functions

本章描述了所有辅助函数,这些函数在执行套接字编程时使用。其他辅助函数在章节中描述 - Ports and Services 和网络 Byte Orders

This chapter describes all the helper functions, which are used while doing socket programming. Other helper functions are described in the chapters −Ports and Services, and Network Byte Orders.

The write Function

write 函数尝试将 buf 指向的缓冲区中的 nbyte 字节写入与打开的文件描述符 fildes 关联的文件。

The write function attempts to write nbyte bytes from the buffer pointed by buf to the file associated with the open file descriptor, fildes.

您还可以使用 send() 函数向另一个进程发送数据。

You can also use send() function to send data to another process.

#include <unistd.h>

int write(int fildes, const void *buf, int nbyte);

成功完成后,write() 返回实际写入与 fildes 关联的文件的字节数。此数字永远不会大于 nbyte。否则,返回 -1。

Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.

Parameters

  1. fildes − It is a socket descriptor returned by the socket function.

  2. buf − It is a pointer to the data you want to send.

  3. nbyte − It is the number of bytes to be written. If nbyte is 0, write() will return 0 and have no other results if the file is a regular file; otherwise, the results are unspecified.

The read Function

read 函数尝试从与缓冲区 fildes 关联的文件读取 nbyte 字节,并将其读入 buf 指向的缓冲区。

The read function attempts to read nbyte bytes from the file associated with the buffer, fildes, into the buffer pointed to by buf.

您还可以使用 recv() 函数读取另一个进程的数据。

You can also use recv() function to read data to another process.

#include <unistd.h>

int read(int fildes, const void *buf, int nbyte);

成功完成后,write() 返回实际写入与 fildes 关联的文件的字节数。此数字永远不会大于 nbyte。否则,返回 -1。

Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.

Parameters

  1. fildes − It is a socket descriptor returned by the socket function.

  2. buf − It is the buffer to read the information into.

  3. nbyte − It is the number of bytes to read.

The fork Function

fork 函数创建一个新进程。称为子进程的新进程将是调用进程(父进程)的确切副本。子进程从父进程继承许多属性。

The fork function creates a new process. The new process called the child process will be an exact copy of the calling process (parent process). The child process inherits many attributes from the parent process.

#include <sys/types.h>
#include <unistd.h>

int fork(void);

成功完成后,fork() 将 0 返回给子进程,并将子进程的进程 ID 返回给父进程。否则,返回 -1 给父进程,不会创建子进程,并设置 errno 指示错误。

Upon successful completion, fork() returns 0 to the child process and the process ID of the child process to the parent process. Otherwise -1 is returned to the parent process, no child process is created and errno is set to indicate the error.

Parameters

  1. void − It means no parameter is required.

The bzero Function

bzero 函数在字符串 s 中放置 nbyte 个空字节。此函数用于使用空值设置所有套接字结构。

The bzero function places nbyte null bytes in the string s. This function is used to set all the socket structures with null values.

void bzero(void *s, int nbyte);

此函数不会返回任何内容。

This function does not return anything.

Parameters

  1. s − It specifies the string which has to be filled with null bytes. This will be a point to socket structure variable.

  2. nbyte − It specifies the number of bytes to be filled with null values. This will be the size of the socket structure.

The bcmp Function

bcmp 函数将字节字符串 s1 与字节字符串 s2 进行比较。假设这两个字符串都是 nbyte 字节长。

The bcmp function compares byte string s1 against byte string s2. Both strings are assumed to be nbyte bytes long.

int bcmp(const void *s1, const void *s2, int nbyte);

如果这两个字符串相同,则此函数返回 0,否则返回 1。如果 nbyte 为 0,bcmp() 函数始终返回 0。

This function returns 0 if both strings are identical, 1 otherwise. The bcmp() function always returns 0 when nbyte is 0.

Parameters

  1. s1 − It specifies the first string to be compared.

  2. s2 − It specifies the second string to be compared.

  3. nbyte − It specifies the number of bytes to be compared.

The bcopy Function

bcopy 函数将 nbyte 字节从字符串 s1 复制到字符串 s2。重叠字符串得到了正确的处理。

The bcopy function copies nbyte bytes from string s1 to the string s2. Overlapping strings are handled correctly.

void bcopy(const void *s1, void *s2, int nbyte);

此函数不会返回任何内容。

This function does not return anything.

Parameters

  1. s1 − It specifies the source string.

  2. s2v − It specifies the destination string.

  3. nbyte − It specifies the number of bytes to be copied.

The memset Function

memset 函数还用于像 bzero 一样设置结构变量。请看下面给出的语法。

The memset function is also used to set structure variables in the same way as bzero. Take a look at its syntax, given below.

void *memset(void *s, int c, int nbyte);

此函数返回一个指向 void 的指针;实际上是一个指向已设置内存的指针,并且需要相应地对其进行转换。

This function returns a pointer to void; in fact, a pointer to the set memory and you need to caste it accordingly.

Parameters

  1. s − It specifies the source to be set.

  2. c − It specifies the character to set on nbyte places.

  3. nbyte − It specifies the number of bytes to be set.

Unix Socket - Server Examples

若要使进程成为 TCP 服务器,您需要遵循以下步骤 −

To make a process a TCP server, you need to follow the steps given below −

  1. Create a socket with the socket() system call.

  2. Bind the socket to an address using the bind() system call. For a server socket on the Internet, an address consists of a port number on the host machine.

  3. Listen for connections with the listen() system call.

  4. Accept a connection with the accept() system call. This call typically blocks until a client connects with the server.

  5. Send and receive data using the read() and write() system calls.

现在我们将这些步骤放在源代码的形式中。将此代码添加到文件 server.c 中,并使用 gcc 编译器进行编译。

Now let us put these steps in the form of source code. Put this code into the file server.c and compile it with gcc compiler.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.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;
}

Handle Multiple Connections

为了允许服务器处理多个同时连接,我们在上述代码中进行了以下更改 -

To allow the server to handle multiple simultaneous connections, we make the following changes in the above code −

  1. Put the accept statement and the following code in an infinite loop.

  2. After a connection is established, call fork() to create a new process.

  3. The child process will close sockfd and call doprocessing function, passing the new socket file descriptor as an argument. When the two processes have completed their conversation, as indicated by doprocessing() returning, this process simply exits.

  4. The parent process closes newsockfd. As all of this code is in an infinite loop, it will return to the accept statement to wait for the next connection.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

void doprocessing (int sock);

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int n, pid;

   /* 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 */
}

以下代码片段展示了 doprocessing 函数的简单实现。

The following code seqment shows a 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 Socket - Client Examples

为了使进程成为 TCP 客户端,您需要按照以下步骤操作:

To make a process a TCP client, you need to follow the steps given below &minus ;

  1. Create a socket with the socket() system call.

  2. Connect the socket to the address of the server using the connect() system call.

  3. Send and receive data. There are a number of ways to do this, but the simplest way is to use the read() and write() system calls.

现在我们将这些步骤放在源代码的形式中。将此代码添加到文件 client.c 中,并使用 gcc 编译器进行编译。

Now let us put these steps in the form of source code. Put this code into the file client.c and compile it with gcc compiler.

运行此程序并传递服务器的主机名和端口号,以连接到服务器,您必须已在另一个 Unix 窗口中运行服务器。

Run this program and pass hostname and port number of the server, to connect to the server, which you already must have run in another Unix window.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.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, (struct sockaddr*)&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 */
   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;
}

Unix Socket - Summary

以下是与套接字编程相关的所有函数的列表。

Here is a list of all the functions related to socket programming.

Port and Service Functions

Unix 提供下列函数用于从 /etc/services 文件中获取服务名称。

Unix provides the following functions to fetch service name from the /etc/services file.

  1. struct servent *getservbyname(char *name, char *proto) − This call takes a service name and a protocol name and returns the corresponding port number for that service.

  2. struct servent *getservbyport(int port, char *proto) − This call takes a port number and a protocol name and returns the corresponding service name.

Byte Ordering Functions

  1. unsigned short htons (unsigned short hostshort) − This function converts 16-bit (2-byte) quantities from host byte order to network byte order.

  2. unsigned long htonl (unsigned long hostlong) − This function converts 32-bit (4-byte) quantities from host byte order to network byte order.

  3. unsigned short ntohs (unsigned short netshort) − This function converts 16-bit (2-byte) quantities from network byte order to host byte order.

  4. unsigned long ntohl (unsigned long netlong) − This function converts 32-bit quantities from network byte order to host byte order.

IP Address Functions

  1. int inet_aton (const char *strptr, struct in_addr *addrptr) − This function call converts the specified string, in the Internet standard dot notation, to a network address, and stores the address in the structure provided. The converted address will be in Network Byte Order (bytes ordered from left to right). It returns 1 if the string is valid and 0 on error.

  2. in_addr_t inet_addr (const char *strptr) − This function call converts the specified string, in the Internet standard dot notation, to an integer value suitable for use as an Internet address. The converted address will be in Network Byte Order (bytes ordered from left to right). It returns a 32-bit binary network byte ordered IPv4 address and INADDR_NONE on error.

  3. char *inet_ntoa (struct in_addr inaddr) − This function call converts the specified Internet host address to a string in the Internet standard dot notation.

Socket Core Functions

  1. int socket (int family, int type, int protocol) − This call returns a socket descriptor that you can use in later system calls or it gives you -1 on error.

  2. int connect (int sockfd, struct sockaddr *serv_addr, int addrlen) − The connect function is used by a TCP client to establish a connection with a TCP server. This call returns 0 if it successfully connects to the server, otherwise it returns -1.

  3. int bind(int sockfd, struct sockaddr *my_addr,int addrlen) − The bind function assigns a local protocol address to a socket. This call returns 0 if it successfully binds to the address, otherwise it returns -1.

  4. int listen(int sockfd, int backlog) − The listen function is called only by a TCP server to listen for the client request. This call returns 0 on success, otherwise it returns -1.

  5. int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen) − The accept function is called by a TCP server to accept client requests and to establish actual connection. This call returns a non-negative descriptor on success, otherwise it returns -1.

  6. int send(int sockfd, const void *msg, int len, int flags) − The send function is used to send data over stream sockets or CONNECTED datagram sockets. This call returns the number of bytes sent out, otherwise it returns -1.

  7. int recv (int sockfd, void *buf, int len, unsigned int flags) − The recv function is used to receive data over stream sockets or CONNECTED datagram sockets. This call returns the number of bytes read into the buffer, otherwise it returns -1 on error.

  8. int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen) − The sendto function is used to send data over UNCONNECTED datagram sockets. This call returns the number of bytes sent, otherwise it returns -1 on error.

  9. int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen) − The recvfrom function is used to receive data from UNCONNECTED datagram sockets. This call returns the number of bytes read into the buffer, otherwise it returns -1 on error.

  10. int close (int sockfd) − The close function is used to close a communication between the client and the server. This call returns 0 on success, otherwise it returns -1.

  11. int shutdown (int sockfd, int how) − The shutdown function is used to gracefully close a communication between the client and the server. This function gives more control in comparison to close function. It returns 0 on success, -1 otherwise.

  12. int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) − This function is used to read or write multiple sockets.

Socket Helper Functions

  1. int write (int fildes, const void *buf, int nbyte) − The write function attempts to write nbyte bytes from the buffer pointed to by buf to the file associated with the open file descriptor, fildes. Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.

  2. int read (int fildes, const void *buf, int nbyte) − The read function attempts to read nbyte bytes from the file associated with the open file descriptor, fildes, into the buffer pointed to by buf. Upon successful completion, write() returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.

  3. int fork (void) − The fork function creates a new process. The new process, called the child process, will be an exact copy of the calling process (parent process).

  4. void bzero (void *s, int nbyte) − The bzero function places nbyte null bytes in the string s. This function will be used to set all the socket structures with null values.

  5. int bcmp (const void *s1, const void *s2, int nbyte) − The bcmp function compares the byte string s1 against the byte string s2. Both the strings are assumed to be nbyte bytes long.

  6. void bcopy (const void *s1, void *s2, int nbyte) − The bcopy function copies nbyte bytes from the string s1 to the string s2. Overlapping strings are handled correctly.

  7. void *memset(void *s, int c, int nbyte) − The memset function is also used to set structure variables in the same way as bzero.