Socket 入门
1. 什么是Socket
1.1. Socket 的引入
我们知道进程通信的方法有管道
1.2. Socket 的位置
socket屏蔽了各个协议的通信细节
<img src=“https://naturalifica.oss-cn-nanjing.aliyuncs.com/~/Users/wuchentian/SoloLearning/Blog/source/imgs202211011749905.png” alt=“图片来源于
1.3. Socket 的定义
socket 一词的起源在组网领域的首次使用是在1970年2月12日发布的文献 IETF RFC33 中发现的
1.4. Socket 的分类
流套接字 (Stream Socket): 主要用于 TCP 协议; 提供了双向的
数据报套接字: 主要用于 UDP 协议; 它提供了双向的
原始套接字: 主要用于访问底层协议
2. Socket 常用函数
注意这里的函数都是包含在 C 语言 <socket.h> 和 <type.h> 头文件中的函数
2.1. socket()
int socket(int protofamily, int so_type, int protocol);
- 点击这里或者查看这篇文章可以查看详情
- protofamily 指协议族
常见的值有, : - AF_INET
指定 so_pcb 中的地址要采用 ipv4 地址类型, - AF_INET6
指定 so_pcb 中的地址要采用 ipv6 的地址类型, - AF_LOCAL/AF_UNIX
指定 so_pcb 中的地址要使用绝对路径名,
- AF_INET
- so_type 指定 socket 的类型
常见的值有, : - SOCK_STREAM
基于 TCP的( 数据传输比较有保障, ) - SOCK_DGRAM (是基于 UDP 的
专门用于局域网),
- SOCK_STREAM
- protocol 指定具体的协议
常见的值有, : - IPPROTO_TCP
TCP 协议, - IPPROTO_UDP
UPD 协议, - 0
如果指定为0, 表示由内核根据so_type指定默认的通信协议,
- IPPROTO_TCP
值得注意的是
2.2. bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd
即socket描述字: 它是通过socket()函数创建了, 唯一标识一个socket, bind()函数就是将给这个描述字绑定一个名字。 。 - addr
一个: const struct sockaddr *
指针 指向要绑定给sockfd的协议地址, 这个地址结构根据地址创建socket时的地址协议族的不同而不同。 。 - addrlen
对应的是地址的长度: 。
2.3. listen()
& connect()
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
如果作为一个服务器
listen函数的第一个参数即为要监听的socket描述字
connect函数的第一个参数即为客户端的socket描述字
2.4. accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept()
函数的第一个参数为服务器的socket描述字struct sockaddr *
的指针
如果 accpet
成功
TCP 服务器端依次调用 socket()
bind()
listen()
之后socket()
connect()
之后就想 TCP 服务器发送了一个连接请求accept()
函数取接收请求
之后就可以开始网络I/O操作了
2.5. read()
& write()
网络I/O操作有下面几组
read();write();
recv();send();
readv();writev();
recvmsg();sendmsg();
recvfrom();sendto();
负责相应的数据读写操作
2.6. close()
int close(int fd);
在服务器与客户端建立连接之后fclose()
关闭打开的文件
3. Java Socket 常用 API
3.1. INetAddress 类 —— 套接字地址/域名解析
-
Java使用InetAddress表示IP地址
-
定义于
java.net
包下 -
既可以表示 IPv4 的地址
也可以表示 IPv6 的地址,
-
-
在 Java 的 Socket API 中
往往将一个, InetAddress
对象和一个端口号一起使用作为套接字地址 -
通过
InetAddress
的静态方法getByName()
可以将一个IP地址或域名转换为InetAddress
对象- 当对应域名不存在或无法解析时
会抛出, java.net.UnknownHostException
异常 需要对其进行处理,
- 当对应域名不存在或无法解析时
-
举例如下
import java.net.*;
class Lookup {
public static void main(String[] args) {
try {
InetAddress a = InetAddress.getByName(args[0]);
System.out.println(args[0] + ":" + a.getHostAddress());
} catch (UnknownHostException e) {
System.out.println("No address found for " + args[0]);
}
}
}
//有输出如下
----------------------------------------
> java Lookup software.nju.edu.cn
software.nju.edu.cn:219.219.120.45
> java Lookup 127.0.0.1
127.0.0.1:127.0.0.1
-----------------------------------------
3.2. Socket/ServerSocket 类 —— TCP套接字
![](https://naturalifica.oss-cn-nanjing.aliyuncs.com/~/Users/wuchentian/SoloLearning/Blog/source/imgs202211012229523.png)
3.2.1. Socket
Socket 类表示一个建立好的TCP连接
//客户端主动连接
new Socket(InetAddress addr, int port)
//当目标机器不可达<span class="bd-box"><h-char class="bd bd-beg"><h-inner>、</h-inner></h-char></span>连接被重置或拒绝等情况时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>会抛出java.io.IOException异常
//与远程机器通信
InputStream getInputStream()//从远程机器读数据
OutputStream getOutputStream()//向远程机器写数据
void close()//关闭该套接字连接
3.2.2. ServerSocket
ServerSocket 类表示服务端创建的等待客户端来连接的TCP套接字
//绑定ServerSocket到本地端口并监听
new ServerSocket(int port) //backlog=50
new ServerSocket(int port, int backlog)
new ServerSocket(int port, int backlog, InetAddress bindAddr)
//当绑定失败<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>譬如端口被占用时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>均抛出java.io.IOException
//通过accept调用获取一个完成三次握手的TCP连接
Socket accept()
//创建时的backlog参数表示允许完成三次握手但没被accept调用获取到的TCP连接个数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>超出backlog的连接将会被拒绝
3.3. InputStream/OutputStream —— 数据的读取和写出
3.3.1. InputStream
- InputStream类是 Java 提供的输入流抽象
- 定义于
java.io
包下 - 输入流有流终止和传输错误两种情况
而一般而言前者不抛出异常, 后者抛出, java.io.IOException
- 通过
read()
方法进行读取
- 定义于
abstract int read()//从输入流读取单个字节<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>当读取成功时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>返回0-255的整数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>当流终止时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>返回-1
int read(byte[] b)//从输入流读取最多b.length个字节<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>并返回读取到b的元素的个数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>当流终止时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>返回-1
int read(byte[] b, int off, int len)//从输入流读取最多len个字节到b中以b[off]开头的存储空间中<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>当流终止时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>返回-1
通过实现 InputStream 类
通过包装 InputStream 类
关于更多的Java IO
3.3.2 OutputStream
OutputStream类为Java提供的输出流抽象
- 定义于
java.io
包下 - 由于数据流的流终止由调用者决定
因此只存在传输错误一种情况, 会抛出, java.io.IOException
异常 - 通过
write()
方法进行写出
abstract void write(int b)//写出单个字节到输出流<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>只有低8位有效
void write(byte[] b)//将b中b.length个字节写出到输出流
void write(byte[] b, int off, int len)//将b中b[off]开头的len个字节写出到输出流
//通过close方法关闭/终止输出流
//子类通过重写close方法决定close时的行为
通过实现 OutputStream 类
通过包装 OutputStream 类
3.4. DatagramSocket/DatagramPacket —— UDP套接字
3.4.1. DatagramSocket
DatagramSocket 类表示一个 UDP 套接字
//绑定UDP套接字到本地端口
//当绑定失败时<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>均会抛出java.net.SocketException异常
new DatagramSocket()//绑定套接字到任意端口
new DatagramSocket(int port)//绑定套接字到指定端口
new DatagramSocket(int port, InetAddress addr)//绑定套接字到指定IP地址和端口
close()//关闭当前UDP套接字<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>释放其占用的端口
//发送和接收数据报文
send(DatagramPacket p)//发送UDP报文
receive(DatagramPacket p)//接收UDP报文<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>会一直阻塞接收到报文<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>或者在设置了超时时间后<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>抛出java.net.SocketTimeoutException
setSoTimeout(int timeout)//设置接收UDP报文的超时时间
3.4.2 DatagramPacket
DatagramPacket类封装了一个由UDP套接字传输的数据包
- 一个缓冲区
用于存放UDP报文数据: - 目标机器的IP地址和端口
当DatagramSocket 的 connect 方法被调用后: DatagramPacket 中的目标 IP 和端口将被忽略, 直到DatagramSocket 的 disconnect 方法被调用,
//创建DatagramPacket的载荷
new DatagramPacket(byte[] buf, int len)//创建一个用于接收的DatagramPacket类<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>超出len的UDP数据将被截断
new DatagramPacket(byte[] buf, int len)//创建一个用于发送的DatagramPacket类
4. 包含 TCP/UDP 的 Socket 使用
4.1. TCP 套接字编程实例
- 客户端从标准输入流读取一行字符串
并将其写出到服务端, - 服务端读取客户端的输入数据
将其转换为大写, 并传回给客户端, - 客户端从服务端读取数据
并将转换后的字符串输出到标准输出流, 回显给用户,
![](https://naturalifica.oss-cn-nanjing.aliyuncs.com/~/Users/wuchentian/SoloLearning/Blog/source/imgs202211012331462.png)
- 如下
package Client
import java.io.*;
import java.net.*;
class TCPClient {
public static void main(String argv[]) throws Exception{
String sentence;
String modifiedSentence;
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
Socket clientSocket = new Socket("hostname", 6789);
DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
sentence = inFromUser.readLine();
outToServer.writeBytes(sentence + '\n');
modifiedSentence = inFromServer.readLine();
System.out.println("FROM SERVER: " + modifiedSentence);
clientSocket.close();
}
}
package Server
import java.io.*;
import java.net.*;
class TCPServer {
public static void main(String argv[]) throws Exception{
String clientSentence;
String capitalizedSentence;
ServerSocket welcomeSocket = new ServerSocket(6789);
while(true) {
Socket connectionSocket = welcomeSocket.accept();
BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
clientSentence = inFromClient.readLine();
capitalizedSentence = clientSentence.toUpperCase() + '\n';
outToClient.writeBytes(capitalizedSentence);
}
}
}
4.2. UDP 套接字编程实例
![](https://naturalifica.oss-cn-nanjing.aliyuncs.com/~/Users/wuchentian/SoloLearning/Blog/source/imgs202211012331209.png)
- 如下
package Client
import java.io.*;
import java.net.*;
class UDPClient {
public static void main(String args[]) throws Exception{
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket clientSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName("hostname");
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
String sentence = inFromUser.readLine();
sendData = sentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
clientSocket.send(sendPacket);
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String modifiedSentence = new String(receivePacket.getData());
System.out.println("FROM SERVER:" + modifiedSentence);
clientSocket.close();
}
}
package Server
import java.io.*;
import java.net.*;
class UDPServer {
public static void main(String args[]) throws Exception{
DatagramSocket serverSocket = new DatagramSocket(9876);
byte[] receiveData = new byte[1024];
byte[] sendData = new byte[1024];
while(true){
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
String sentence = new String(receivePacket.getData());
InetAddress IPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
String capitalizedSentence = sentence.toUpperCase();
sendData = capitalizedSentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
serverSocket.send(sendPacket);
}
}
}