15 套接字

15.1 什么是套接字

套接字是管道概念的一个扩展。套接字不仅可以在本地的进程间进行同行,还可以在不同的计算机之间远程通信。套接字把客户端和服务器区分开来,可以把多个客户连接到一个服务器。

15.2 套接字连接

服务端步骤:

  1. 服务器程序调用socket创建一个套接字

  2. 调用bind给套接字取名字。一般本地套接字的名字是linux系统的文件名,网络套接字则绑定在端口号上。然后服务器就开始等待客户端连接到这个命名套接字。

  3. 调用listen,创建一个队列并将其用于存放来自客户端进入的连接。

  4. 调用accept来接收客户端的连接。服务器调用accept时,会创建一个和原有名字不同的新的套接字。原有的套接字继续等待客户端的连接。

客户端步骤:

  1. 调用socket创建一个未命名的套接字

  2. 将服务器的命名套接字作为一个地址来调用connect与服务器进行连接。

实验:文件系统套接字

我们先后启动server和client边可以看到结果

client

#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';
/* 创建客户端套接字 */
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
/* 填写服务器套接字的地址 */
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");
len = sizeof(address);
result = connect(sockfd, (struct sockaddr *)&address, len);
if (result == -1) {
perror("oops: client1");
exit(1);
}
/* 进行读写 */
write(sockdf, &ch, 1);
read(sockdf, &ch, 1);
printf("char from server = %c\n", ch);
close(sockdf);
exit(0);
}

server

#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;
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
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);
listen(server_sockfd, 5);
while (1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, (socklen_t *)&client_len);
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}

套接字的属性

套接字的特性由三个属性确定:

  • domain(域)

    指套接字中使用的网络介质

  • type(类型)

    不同的套接字域提供了不同的通信方式,AF_UNIX提供可靠的双向通信路径。AF_INET则提供了流套接字(SOCK_STREAM,使用tcp)和数据报套接字(SOCK_DGRAM,使用udp)

  • protocol 协议

    协议由套接字的域和类型决定。一般参数设置为0,表示使用默认协议。

说明

AF_UNIX

UNIX域协议(文件系统套接字)

AF_INET

ARPA因特网协议(UNIX网络套接字)

AF_ISO

ISO标准协议

AF_NS

施乐Xerox网络系统协议

socket 创建套接字

socket函数返回一个描述符,很类似底层的文件描述符。

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

bind 命名套接字

bind系统调用把address中的地址分配给socket关联的未命名的套接字。地址的格式和长度取决于地址族,bind调用把地址转化为一个通用的指向地址的指针(struct sockaddr *)。bind成功返回0,失败返回-1。

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);

每个套接字域都有自己的地址格式

  • AF_UNIX域的地址结构定义在头文件sys/un.h

struct sockaddr_un {
sa_family_t sun_family;
char sun_path[];
};
  • AF_INET域的地址结构定义在头文件netinet/in.h

struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
}

IP地址结构in_addr被定义为:

struct in_addr {
unsigned long int s_addr;
}

listen(服务器)创建套接字队列

listen创建一个队列来保存未处理的请求。第二个参数设置队列的长度。如果如果超过限制,则再往后的连接将被拒绝。

#include <sys/socket.h>
int listen(int socket, int backlog);

accept(服务器)接受连接

#include <sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);

如果套接字队列中没有未处理的连接,accetp将阻塞直到有客户端建立连接为止。我们可以通过对套接字文件描述符设置O_NONBLOCK标志来改变这一行为,用函数fcntl

int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK | flags);

connect(客户端)建立连接

如果不能建立连接,connect调用将阻塞一段时间,如果超时,连接将被放弃,connect调用失败。

#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);

网络套接字

下面我们用网络套接字重写前面的实验。注意不同计算机的字节序可能不同,我们使用htons这类函数把数据统一转换成网络字节序。网络字节序就是大端字节序(big endian),即人类正常从左到右的阅读顺序。

/* client3 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(9734);
len = sizeof(address);
result = connect(sockfd, (struct sockaddr *)&address, len);
if (result == -1) {
perror("oops: client3");
exit(1);
}
/* 进行读写 */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
/* server3 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
while (1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, (socklen_t *)&client_len);
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}