转载出处:https://www.cnblogs.com/dongguacai/p/5747603.html
https://blog.csdn.net/OscarNile/article/details/78578285
Java中的Socket编程接口介绍
Java为Socket编程封装了几个重要的类。
Socket类
Socket类实现了一个客户端socket,作为两台机器通信的终端,默认采用的传输层协议为TCP,是一个可靠传输的协议。Socket类除了构造函数返回一个socket外,还提供了connect, getOutputStream, getInputStream和close方法。connect方法用于请求一个socket连接,getOutputStream用于获得写socket的输出流,getInputStream用于获得读socket的输入流,close方法用于关闭一个流。
DatagramSocket类
DatagramSocket类实现了一个发送和接收数据报的socket,传输层协议使用UDP,不能保证数据报的可靠传输。DataGramSocket主要有send, receive和close三个方法。send用于发送一个数据报,Java提供了DatagramPacket对象用来表达一个数据报。receive用于接收一个数据报,调用该方法后,一直阻塞接收到直到数据报或者超时。close是关闭一个socket。
ServerSocket类
ServerSocket类实现了一个服务器socket,一个服务器socket等待客户端网络请求,然后基于这些请求执行操作,并返回给请求者一个结果。ServerSocket提供了bind、accept和close三个方法。bind方法为ServerSocket绑定一个IP地址和端口,并开始监听该端口。accept方法为ServerSocket接受请求并返回一个Socket对象,accept方法调用后,将一直阻塞直到有请求到达。close方法关闭一个ServerSocket对象。
SocketAddress
SocketAddress提供了一个socket地址,不关心传输层协议。这是一个虚类,由子类来具体实现功能、绑定传输协议。它提供了一个不可变的对象,被socket用来绑定、连接或者返回数值。
InetSocketAddress
InetSocketAddress实现了IP地址的SocketAddress,也就是有IP地址和端口号表达Socket地址。如果不制定具体的IP地址和端口号,那么IP地址默认为本机地址,端口号随机选择一个。
DatagramPacket
DatagramSocket是面向数据报socket通信的一个可选通道。数据报通道不是对网络数据报socket通信的完全抽象。socket通信的控制由DatagramSocket对象实现。DatagramPacket需要与DatagramSocket配合使用才能完成基于数据报的socket通信。
简易服务端、客户端模拟-TCP
服务器端
package com.zhihua.tcp;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class SocketTcp_server {
static private String TAG = "SocketTcp: ";
public static void main(String[] args){
try {
// 也可以这样创建一个socket服务端,自动绑定本地ip,端口号8888
//ServerSocket serverSocket = new ServerSocket(8888);
ServerSocket server = new ServerSocket();
SocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 10001);
server.bind(address);
System.out.println("==waiting for being connected...");
Socket client = server.accept();
System.out.println("==connected with " + client.getRemoteSocketAddress() );
System.out.println("==waiting message from client...");
byte buf[] = new byte[1024];
if ( client.getInputStream().read(buf) > 0 ) {
System.out.println("Receive Message: " + new String(buf));
}
System.out.println("==sending message to client...");
// 两种发送信息的方式
String sendStr = "This is the message for client.";
PrintWriter socketOut = new PrintWriter(client.getOutputStream());
socketOut.write(sendStr);
socketOut.flush();
PrintStream printStream = new PrintStream(client.getOutputStream());
printStream.println("服务端发送给客户端");
printStream.close();
socketOut.close();
client.close();
server.close();
} catch (IOException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
}
}
}
客户端
package com.zhihua.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
public class SocketTcp_client {
static private String TAG = "SocketTcp: ";
public static void main(String[] args){
try {
// 也可以这样创建一个socket
// Socket socketClient = new Socket("localhost", 8888);
final Socket socket = new Socket();
SocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 10001);
System.out.println("==connecting to server ...");
socket.connect(address);
PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
BufferedReader socketIn = new BufferedReader(
new InputStreamReader(socket.getInputStream()) );
String sendStr = "This is the message for server.";
System.out.println("==sending message to server ...");
socketOut.write(sendStr);
socketOut.flush();
System.out.println("==waiting message from server ...");
String receiveStr = socketIn.readLine();
System.out.println("Receive Message: " + receiveStr);
socketOut.close();
socketIn.close();
socket.close();
} catch (IOException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
} finally {
}
}
}
- 上面展示的是一个简易的服务端和客户端通信的建立过程。
我们来通过这个交互图来详细介绍这个过程:
首先在server端,指定端口号创建serverSocket对象,通过serverSocket的accpet方法获取套接字,这个方法的特点是:侦听并接受此套接字的连接,此方法在连接传入之前一直阻塞。这也就意味着,如果没有客户端连接请求过来,服务端会一直阻塞在这里。
- 后面的代码就是通过套接字socket可以得到输入输出流,到此为止,就是I/O的内容了。
- 在客户端这边,通过指定的服务器主机名和服务器监听的端口号,得到套接字Socket,这个时候服务端和客户端的连接已经建立了,然后通过输入输出流来进行通信了。
半关闭的socket
在上面的Demo中,我们是以行作为通信的最小数据单位,服务端也是逐行进行处理的。但是我们在大多数场景下,通信的数据单位是多行的,这时候,Socket的输出流如何表达输出的数据已经结束??
在IO学习过程中提到过,如何要表示输出已经结束,则通过关闭输出流来实现,但是在socket中是行不通的,因为关闭socket,会导致无法再从该socket中读取数据了。为了解决这种问题,java提供了两个半关闭的方法:
1、shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。
2、shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流读取数据。
如果我们对同一个Socket实例先后调用shutdownInput和shutdownOutput方法,该Socket实例依然没有被关闭,只是该Socket既不能输出数据,也不能读取数据。
服务器端:
package com.zhihua.socket;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Socket_Server_2 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7777);
System.out.println("服务器已经开启");
Socket socket = serverSocket.accept();
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("服务端:开源中国杭州论坛");
ps.println("服务端:杭州G20峰会");
// 关闭输出流,表明输出已经结束
socket.shutdownOutput();
// 判断该socket是否关闭
System.out.println(socket.isClosed());
Scanner scanner = new Scanner(socket.getInputStream());
while(scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
scanner.close();
socket.close();
serverSocket.close();
}
}
客户端:
package com.zhihua.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class Socket_Client_2 {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("localhost", 7777);
InputStream is = socket.getInputStream();
byte[] buffer = new byte[1024];
int flag = 0;
while(-1 != (flag = is.read(buffer,0,buffer.length))) {
String str = new String(buffer,0,flag);
System.out.println(str);
}
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("客户端:欢迎参加开源中国论坛");
ps.println("客户端:欢迎参加G20峰会");
is.close();
ps.close();
socket.close();
}
}
在服务器端程序中可以看到,在输出两段字符串之后,调用了shutdownOutput方法,表示输出已经结束。随即又去判断了socket是否关闭,执行的结果为false,表示socket并未关闭。
但是在调用了这两个半关闭的方法关闭了输出输入流之后,该socket无法再次打开该输出流或者输入流。因此这种场景不适合保持持久通信状态的交互使用,只适合一站式的通信协议.例如http协议:客户端连接到服务器之后,开始发送数据,发送完成之后无须再次发送数据,只需要读取服务器响应数据即可,读取数据完毕之后,该socket连接也被关闭了。
基于UDP协议的网络编程
前面介绍的socket编程都是基于TCP协议的,现在来看下基于UDP协议的编程,TCP和UDP的区别在上一章已经有过介绍。
UDP协议的主要作用就是完成网络数据流和数据报之间的转换—–在信息的发送端,UDP协议将网络数据流封装到数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。
1、首先在UDP网络编程中没有服务器端和客户端这种说法,两个socket之间没有虚拟链路,只是接收和发送数据报文而已。
2、这里面有两个重要的类:DatagramSocket 和DatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该数据包中的包含的信息从一台机器路由到另一台机器。
3、DatagramSocket 的两个构造函数:
DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。
4、DatagramPacket:创建的时候分为接收和发送两种
DatagramPacket(byte[] buf, int length):用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port):
用来将长度为 length 的包发送到指定主机上的指定端口号。
package com.zhihua.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SocketUdp_server {
final private static String TAG = "SocketUdp: ";
public static void main(String args[]) {
DatagramSocket socket = null;
DatagramPacket datapacket = null;
InetSocketAddress address = null;
try {
address = new InetSocketAddress(InetAddress.getLocalHost(), 7778);
socket = new DatagramSocket(address);
// socket.bind(address);
byte buf[] = new byte[1024];
datapacket = new DatagramPacket(buf, buf.length);
System.out.println("==block for receive messages...");
socket.receive(datapacket);
buf = datapacket.getData();
InetAddress addr = datapacket.getAddress();
int port = datapacket.getPort();
System.out.println("Message Content: " + new String(buf) );
System.out.println("Receive From " + addr + ":" + port);
SocketAddress toAddress = datapacket.getSocketAddress();
String sendStr = "I'm Server, this is the message for client.";
buf = sendStr.getBytes();
datapacket = new DatagramPacket(buf, buf.length);
datapacket.setSocketAddress(toAddress);
socket.send(datapacket);
System.out.println("==message sended");
} catch (UnknownHostException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
} catch (SocketException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
}
}
}
package com.zhihua.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SocketUdp_client {
final private static String TAG = "SocketUdp: ";
public static void main(String args[]) {
try {
DatagramSocket getSocket = new DatagramSocket();
DatagramPacket datapacket = null;
InetSocketAddress toAddress =
new InetSocketAddress(InetAddress.getLocalHost(), 7778);
String sendStr = "I'm client, this is the message for server.";
byte buf[] = sendStr.getBytes();
datapacket = new DatagramPacket(buf, buf.length);
datapacket.setSocketAddress(toAddress);
getSocket.send(datapacket);
System.out.println("==message sended");
System.out.println("==block for receive messages...");
getSocket.receive(datapacket);
buf = datapacket.getData();
System.out.println("Message Content: " + new String(buf));
} catch (SocketException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
} catch (UnknownHostException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.out.println(TAG + e.getMessage());
e.printStackTrace();
}
}
}
单服务端,多客户端
以上代码实现了单客户端和服务端的连接,若要实现多客户端操作,需要涉及到多线程,只要你把每个接收到的Socket对象单独开一条线程操作,然后用一个死循环while(true)去监听端口就行。
SocketThread.java
package com.zhihua.socket.thread;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* Socket多线程处理类 用来处理服务端接收到的客户端请求(处理Socket对象)
*/
public class SocketThread extends Thread {
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
public void run() {
// 根据输入输出流和客户端连接
try {
InputStream inputStream = socket.getInputStream();
// 得到一个输入流,接收客户端传递的信息
InputStreamReader inputStreamReader =
new InputStreamReader(inputStream);// 提高效率,将自己字节流转为字符流
BufferedReader bufferedReader =
new BufferedReader(inputStreamReader);// 加入缓冲区
String temp = null;
String info = "";
while ((temp = bufferedReader.readLine()) != null) {
info += temp;
System.out.println("已接收到客户端连接");
System.out.println("服务端接收到客户端信息:" + info
+ ",当前客户端ip为:"
+ + socket.getInetAddress().getHostAddress());
}
OutputStream outputStream =
socket.getOutputStream();// 获取一个输出流,向服务端发送信息
PrintWriter printWriter =
new PrintWriter(outputStream);// 将输出流包装成打印流
printWriter.print("你好,服务端已接收到您的信息");
printWriter.flush();
socket.shutdownOutput();// 关闭输出流
// 关闭相对应的资源
bufferedReader.close();
inputStream.close();
printWriter.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端类:Server.java
package com.zhihua.socket.thread;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
/**
* Socket服务端
*/
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端已启动,等待客户端连接..");
while (true) {
// 侦听并接受到此套接字的连接,返回一个Socket对象
Socket socket = serverSocket.accept();
SocketThread socketThread = new SocketThread(socket);
socketThread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端类:Client.java
package com.zhihua.socket.thread;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
/**
* Socket客户端
*/
public static void main(String[] args) {
try {
//创建Socket对象
Socket socket=new Socket("localhost",8888);
//根据输入输出流和服务端连接
OutputStream outputStream=
socket.getOutputStream();//获取一个输出流,向服务端发送信息
PrintWriter printWriter=
new PrintWriter(outputStream);//将输出流包装成打印流
printWriter.print("服务端你好,我是客户1");
printWriter.flush();
socket.shutdownOutput();//关闭输出流
InputStream inputStream=
socket.getInputStream();//获取一个输入流,接收服务端的信息
InputStreamReader inputStreamReader=
new InputStreamReader(inputStream);//包装成字符流,提高效率
BufferedReader bufferedReader=
new BufferedReader(inputStreamReader);//缓冲区
String info="";
String temp=null;//临时变量
while((temp=bufferedReader.readLine())!=null){
info+=temp;
System.out.println("客户端接收服务端发送信息:"+info);
}
//关闭相对应的资源
bufferedReader.close();
inputStream.close();
printWriter.close();
outputStream.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里只是抛砖引玉,在实际开发中,基于Socket编程,一般传递的并非字符串,很多情况下是对象,我们可以使用ObjectOutputStream将输出流对象序列化。
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
User user=new User("admin","123456");
objectOutputStream.writeObject(user);