套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocol family)的不同而不同。每个协议族又可以使用一个或多个地址族定义地址格式。
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即IP地址。
在计算机系统内部,端口通过分配一个唯一的16位的整数来表示,在系统外部,则需要通过IP地址和端口号的组合来确定。
2.套接字类型
流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。他们也是AF_UNIX域中常见的套接字类型。
数据包套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。
数据报套接字实在AF_INET域中通过UDP/IP连接实现,它提供的是一种无需的不可靠服务。
3.套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。
创建套接字
socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain , int type , int protocol);
创建的套接字是一条通信线路的一个端点。domain参数指定协议族,type参数指定这个套接字的通信类型,protocol参数指定使用的协议。
domain参数可以指定的协议族如下
域 说明
AF_UNIX UNIX域协议(文件系统套接字)
AF_INET ARPA因特网协议(UNIX网络套接字)
AF_ISO ISO标准协议
AF_NS 施乐(XEROX)网络系统协议
AF_IPX NOVELL IPX协议
AF_APPLETALKAppletalk DDS
最常见的套接字域是AF_UNIX和AF_INET,前者用于通过Unix和Linux文件系统实现的本地套接字,后者用于Unix网络套接字。AF_INET套接字可以用于通过包括因特网在内的TCP/IP网络进行通信的程序。微软Windows系统的winsock接口也提供了对这个套接字域的访问功能。
socket函数的参数type指定用于新套接字的通信特性。它的取值包括SOCK_STREAM和SOCK_DGRAM。
SOCK_STREAM是一个有序、可靠、面向连接的双字节流。通过TCP连接来实现。
SOCK_DGRAM是数据包服务,我们可以用它来发送最大长度固定的消息。但消息是否会被正确传递或消息是否不会乱序到达没有保证。
套接字地址结构
结构struct sockaddr_un 定义了一种通用的套接字地址,它的类型是:
struct sockaddr_un
{
sa_family_t sun_family; /*AF_UNIX*/
char sun_path; /*pathname*/
};
这是一种通用的定义,一般都不用。TCP/IP使用的是自己的结构体struct sockaddr_in,格式如下:
struct sockaddr_in
{
short int sin_family; //地址类型,一般为AF_INET
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
};
这里的struct in_addr的定义如下:
struct in_addr
{
unsigned long int s_addr;
};
结构体sockaddr和sockaddr_in的长度都是16字节。一般在编TCP/IP程序时,一般使用结构体sockaddr_in来设置地址,然后在需要的时候,通过强制类型转换成sockaddr类型。
建立连接
函数connect用来在一个指定的套接字上创建一个连接,函数原型:
[cpp] view plain copy print?
- int connect(int socket, const struct sockaddr *address, size_t address_len);
参数sockfd是一个由函数socket创建的套接字;
参数address是一个地址结构,需要连接的地址;
参数address_len为参数addr_addr的长度。
函数执行成功返回0,有错误发生则返回-1。
如果套接字类型是TCP,则该函数用于向服务器发出连接请求,服务器的IP地址和端口号由参数serv_addr指定;如果套接字类型是UDP,则该函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被该socket接收。
通常一个面向连接的套接字只能调用一次connect函数;而对于无连接的套接字则可以多次调用connect函数以改变与目的地址的绑定。
在套接字上监听
函数listen把套接字转化为被动监听,函数原型:
int listen(int s, int backlog);
参数s指定了一个套接字;
参数backlog指定了该连接队列的最大长度,如果已达到最大,则之后的连接请求将被服务器拒绝。
函数执行成功赶回0,有错误发生则返回-1。
由函数socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器上。(通过connect()函数)。
作为服务器端的程序,通常在某个端口上监听等待来自客户端的连接请求。在服务器端,一般是先调用函数socket创建一个主动套接字,然后调用函数bind将该套接字绑定到某个端口上,接着再调用函数listen将该套接字转化为监听套接字,等待来自于客户端的连接请求。
函数listen只是将套接字设置为倾听模式以等待连接请求,它并不能接收连接请求,真正的接收客户端连接请求的是accept()函数。
接收连接
函数accept用来接收一个连接请求,函数原型:
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
参数s是由socket创建,经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听套接字;
参数addr用来保存发起连接请求的主机的地址和端口;
参数addrlen是addr所指向的结构体的大小。
函数执行成功返回一个新的代表客户端的套接字,出错则返回-1。
只能对面向连接的套接字使用accept函数。accept执行成功时,将创建一个新的套接字,并且这个新的套接字分配一个套接字描述符,并返回这个新的套接字描述符。这个新的套接字描述符与打开文件返回的文件描述符类似,进程可以利用这个新的套接字描述符与客户端交换数据,参数s所指定的套接字继续等待客户端的连接请求。
[cpp] view plain copy print?
- /* Make the necessary includes and set up the variables. */
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int sockfd;
- int len;
- struct sockaddr_un address;
- int result;
- char ch = ‘A‘;
- /* Create a socket for the client. */
- sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
- /* Name the socket, as agreed with the server. */
- address.sun_family = AF_UNIX;
- strcpy(address.sun_path, "server_socket");
- len = sizeof(address);
- /* Now connect our socket to the server‘s socket. */
- result = connect(sockfd, (struct sockaddr *)&address, len);
- if(result == -1) {
- perror("oops: client1");
- exit(1);
- }
- /* We can now read/write via sockfd. */
- write(sockfd, &ch, 1);
- read(sockfd, &ch, 1);
- printf("char from server = %c\n", ch);
- close(sockfd);
- exit(0);
- }
[cpp] view plain copy print?
- /* Make the necessary includes and set up the variables. */
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_un server_address;
- struct sockaddr_un client_address;
- /* Remove any old socket and create an unnamed socket for the server. */
- unlink("server_socket");
- server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
- /* Name the socket. */
- server_address.sun_family = AF_UNIX;
- strcpy(server_address.sun_path, "server_socket");
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
- /* Create a connection queue and wait for clients. */
- listen(server_sockfd, 5);
- while(1) {
- char ch;
- printf("server waiting\n");
- /* Accept a connection. */
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd,
- (struct sockaddr *)&client_address, &client_len);
- /* We can now read/write to client on client_sockfd. */
- read(client_sockfd, &ch, 1);
- ch++;
- write(client_sockfd, &ch, 1);
- close(client_sockfd);
- }
- }