目录
UDP数据报套接字编程
DatagramSocket API
DatagramPacket API
回显C/S示例
TIPS
TCP
ServerSocket API
Socket API
回显C/S示例
UDP数据报套接字编程
DatagramSocket API
socket是操作系统中的一种概念,本质上是一种特殊的文件,socket属于是把“网卡”这个设备给抽象成文件,往socket文件中写数据,就相当于通过网卡发送数据,往socket文件读数据,就相当于通过网卡接收数据,在java中使用DatagramSocket来表示系统内部的socket文件。
DatagramSocket是UDP Socket,用于发送和接收UDP数据报
DatagramSocket构造方法:
方法 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务器) |
DatagramSocket方法:
方法 | 说明 |
---|---|
void send(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void receive(DatagramPacket p) | 从此套接字发送数据报(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket API
DatagramPacket表示一个udp数据报
DatagramPacket构造方法
方法 | 说明 |
---|---|
DatagramPacket(byte[] buf,int length) | 构建一个DatagramPacket以用来接收数据报,接收的数组保存在字节数组(第一个参数buf)中,第二个参数 结束指定长度 |
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address) | 构建一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf),从0到指定长度(第二个参数length),address指定目的主机的ip和端口号 |
DatagramPacket方法
方法 | 说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机ip地址;或从发送的数据中,获取接收端主机ip地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号,或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
服务器和客户端都需要创建Socket对象,但是服务器的socket一般要显示手动指定一个端口号,而客户端的socket一般不能显式指定(不显式指定,此时系统会自动分配一个随机的端口),指定的话可能会与其他客户端冲突,系统自动分配可以避免冲突。
回显C/S示例
服务端代码
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket=null;
//指定服务器socket要绑定的端口
private static final int PORT=9090;
public UdpEchoServer(int port) throws SocketException {
socket=new DatagramSocket(PORT);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true)
{
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//当完成receive之后,数据都是以二进制的形式存储在DatagramPacket中
//因此我们需要先将这个二进制数据转成字符
String request=new String(requestPacket.getData(),0,requestPacket.getLength());
String response=process(request);
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req=%s, resp=%s\n",requestPacket.getAddress().toString(),responsePacket.getPort(),request,response);
}
}
public String process(String request)
{
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer=new UdpEchoServer(9090);
udpEchoServer.start();
}
}
客户端代码
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket=null;
private String serverIP="";
private int serverPort=0;
public UdpEchoClient(String ip,int port) throws SocketException {
socket=new DatagramSocket();
serverIP=ip;
serverPort=port;
}
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner scanner=new Scanner(System.in);
while(true)
{
System.out.println("-> ");
String request=scanner.next();
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.printf("%s\n",response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
效果如下:
TIPS
1、new byte[4096],这个对象用来承载从网卡这边读到的数据,收到数据的时候,需要搞一个内存空间来保存这个数据,DatagramPacket内部不能自行分配内存空间,因此就需要我们手动把空间创建好,交给DatagramPacket进行处理。
2、服务器一旦启动,就会立即执行到这里的receive方法,此时,如果客户端的请求可能还没来呢,这种情况也没关系,receive就会直接阻塞,就会一直阻塞到客户端把请求发过来为止。
3、完成receive之后,数据都是以二进制的形式存储到DatagramPacket中,要想把这里的数据给显示出来,还需要把这个二进制数据给转成字符串
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
- 将0,requestPacket.getLength()区间内的字节,构造成一个String
4、response.getBytes().length()不能写成 response.length(),这涉及到字符集的问题,如果这个字符串的内容都是英文字符,此时字节和字符个数是一样的,但是如果包含中文,就不一样了 ,在网络传输时,都是以字节为单位
TCP
因为tcp是面向字节流的,传输的基本单位是byte,因此不需要像udp一样需要有一个数据报类表示传输的基本单位。
ServerSocket API
ServerSocket构造方法:
方法 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket方法:
方法 | 说明 |
---|---|
Socket accept() | 开始监听指定端口,有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket API
Socket构造方法:
方法 | 说明 |
---|---|
Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应ip的主机上,对应端口的进程建立连接 |
Socket方法:
方法 | 说明 |
---|---|
getPort() | 获取对端的端口 |
getInetAddress() | 获取对端的ip |
getLocalAddress() | 获取本地的ip |
getLocalPort() | 获取本地的端口 |
InputStream getInputStream() | 返回此套接字的输入流(进行read操作,就是接收) |
OutputStream getOutputStream() | 返回此套接字的输出流(进行write操作,就是发送) |
前面udp中DatagramSocket和ServerSocket没写close,是因为程序中,只有这么一个对象,生命周期是贯穿整个程序的,而在服务端accept,与客户端连接,在服务端每次有新的客户端来建立连接,都会创建一个新的clientSocket,每个socket对象会占据文件描述符表的位置,因此我们要记得释放。
回显C/S示例
服务端代码:
这里服务端处理连接时创建新线程处理一个客户端连接,因为tcp连接服务端和客户端交互时,需要保持连接,如果不新建线程进行处理,客户端不能多个同时与服务端进行交互,需要排队等待。
为了减少线程创建销毁的开销,这里引入线程池的使用。
package Tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class TcpServer {
private ServerSocket serverSocket=null;
public TcpServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务端上线了!");
ExecutorService service= Executors.newCachedThreadPool();
while(true)
{
Socket clientSocket=serverSocket.accept();
service.submit(new Runnable() {
@Override
public void run() {
try {
proccessConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
public void proccessConnection(Socket clientSocket) throws IOException {
//进入方法,先打印一个日志,表示当前有客户但上线了
System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort() );
//接下来进行信息交互
try(InputStream inputStream=clientSocket.getInputStream())
{
OutputStream outputStream=clientSocket.getOutputStream();
Scanner scanner = new Scanner(inputStream);
while(true) {
if (!scanner.hasNext())
{
System.out.printf("[%s:%d] 客户端下线了\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1、请求并解析
String request=scanner.next();
//2、根据请求,计算响应
String response=proccess(request);
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(response);
//此处要记得刷新缓冲区,如果没有刷新操作,数据可能还在内存中,没有被写入网卡
printWriter.flush();
System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
clientSocket.close();
}
}
public String proccess(String request)
{
return request;
}
public static void main(String[] args) throws IOException {
TcpServer tcpServer=new TcpServer(9090);
tcpServer.start();
}
}
客户端代码:
package Tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpClient {
private Socket clientSocket=null;
public TcpClient(String serverIP,int port) throws IOException {
clientSocket=new Socket(serverIP,port);
}
public void start()
{
Scanner scaner=new Scanner(System.in);
try(InputStream inputStream=clientSocket.getInputStream())
{
OutputStream outputStream=clientSocket.getOutputStream();
PrintWriter printWriter=new PrintWriter(outputStream);
Scanner scannerNetwork=new Scanner(inputStream);
while(true)
{
//从控制台获取用户输入的内容
System.out.println("-> ");
String request=scaner.next();
//把字符串作为请求,发送给服务器
printWriter.println(request);
printWriter.flush();
//读取服务器返回的响应
String response=scannerNetwork.next();
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpClient tcpClient=new TcpClient("127.0.0.1",9090);
tcpClient.start();
}
}