套接字复习笔记

本篇是对《深入理解计算机系统(原书第三版3)》第十一章网络编程关于套接字的内容的复习笔记。

基本概念

一个套接字是连接的一个端点

每个套接字都有相应的套接字地址:因特网地址 + 一个16位的整数端口组成(格式为地址:端口);

客户端套接字地址中端口通常由内核自动分配,称为临时端口;服务器套接字地址中的端口通常为知名端口;

/etc/services 包含一张本机器提供的知名名字和知名端口之间的映射;

一个连接由套接字对唯一确定;

套接字接口是一组函数,它们和Unix函数结合起来,用以创造网络应用;

AF_INET:IPv4 网络协议的套接字类型;

为什么会出现sockaddr_in和sockaddr?书中给了一个解释是方便定义connect、bind、accept这些函数,使它们能接受各种类型的套接字地址结构;

socket连接机制

  • socket函数:

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

1
clientfd = Socket(AF_INET, SOCK_STREAM, 0);

SOCK_STREAM表示这个套接字是连接的一个端点;

  • connect函数:

客户端通过调用connect函数来建立和服务器的连接

1
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);

其中sockaddr是服务器的套接字地址,addrlen是sizeof(sockaddr_in);

如果连接成功,clientfd描述符就“打开”,准备好可以读写了;

  • bind函数:
1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

将addr中的服务器套接字地址和套接字描述符sockfd联系起来;

  • listen函数:
1
2
3
#include <sys/socket.h>
int listen(int sockfd, int backlog);

listen将sockfd从一个主动套接字转化为一个监听套接字,然后接受来自客户端的连接请求;

backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队完成的连接请求数量,通常设置为一个较大值,比如1024;

  • accept函数:
1
2
3
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);

accept函数等待来自客户端的连接请求到达监听描述符listenfd,然后在addr中填写客户端的套接字地址,并返回一个已连接描述符;

  • getaddrinfo函数:

将主机名、主机地址、服务名和端口号的字符串转化成套接字地址结构;

  • getnameinfo函数:

getnameinfo函数和getaddrinfo是相反的,将一个套接字地址结构转换成相应的主机和服务名字符串;

getaddrinfo和getnameinfo可以用来为一些函数自动生成并提供参数;

套接字的连接机制可以大致用一张图来表示:

基于套接字应用概述

Java编写socket

接下来用Java编写一个简单的socket程序。

首先是客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8091;
try {
Socket client = new Socket(host, port);
OutputStream out = client.getOutputStream();
out.write("你好".getBytes("GBK"));
out.flush();
out.close();
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

在这段代码中并没有connect(),当然,即便有,它也并不是前文中那个connect(),从这点就能看出Java对底层代码进行了封装,如果要模拟底层的构造但不连接形式,可以将Socket client = new Socket(host, port);替换成:

1
2
3
Socket client = new Socket();
SocketAddress address = new InetSocketAddress("127.0.0.1", 8091);
client.connect(address);

接下来再写一个简单的服务器端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Server {
public static void main(String[] args) {
int port = 8091;
try {
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
InputStream input = socket.getInputStream();
byte[] b = new byte[2];
int i = 0;
while ((i = input.read(b)) != -1) {
String str = new String(b, "GBK");
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

ServerSocket将底层的函数封装起来,提供简便的api,运行服务器端,然后执行客户端,服务器端打印出”你好”;

小结

本篇算是对套接字进行了一个简单的复习,了解了套接字的连接机制。

参考

《深入理解计算机系统(原书第3版)》

问题

客户端需要将套接字描述符和客户端套接字地址联系起来吗??

listenfd是怎么产生的?