<网络编程Ⅰ>Socket通信

参考书籍:

  1. 网络是怎样连接的在这里插入图片描述
    很有趣味的一本书,但也没那么容易读通。

  2. NIO与Socket编程技术指南
    在这里插入图片描述
    风评较差的一本书,但对于我这种网络编程菜鸟来说很有用。

文章目录

  • 1.基于TCP的Socket通信
    • 1.1 客户端向服务端传递字符串
    • 1.2 服务端向客户端传递字符串
    • 1.3 实现服务端与客户端的连续多次的长连接通信
    • 1.4 使用Socket传递JPG图片文件
    • 1.5 结合多线程Thread实现通信
    • 1.6 服务端与客户端互传对象以及I/O流顺序问题
  • 2.ServerSocket类的使用
    • 2.1 接受accept与超时Timeout
    • 2.2 构造方法的backlog参数含义
    • 2.3 构造方法ServerSocket(int port, int backlog, InetAddress bindAddr)的使用
  • 3.Socket类的使用
    • 3.1 获得远程端口与本地端口
  • 4.基于UDP的Socket通信

1.基于TCP的Socket通信

1.1 客户端向服务端传递字符串

accept()方法具有阻塞性,此方法在连接传入之前一直阻塞。public Socket accept()方法的返回值是Socket类型。InputStream类中的read()方法也同样具有阻塞特性。

public class Server {public static void main(String[] args) {try {char[] charArray = new char[3];ServerSocket serverSocket = new ServerSocket(8088);Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);int readLength = inputStreamReader.read(charArray);while (readLength != -1) {String newString = new String(charArray, 0, readLength);System.out.println(newString);readLength = inputStreamReader.read(charArray);}inputStreamReader.close();inputStream.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
public class Client {public static void main(String[] args) {try {Socket socket = new Socket("localhost", 8088);Thread.sleep(3000);OutputStream outputStream = socket.getOutputStream();outputStream.write("来自客户端".getBytes());outputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}
}

在这里插入图片描述

1.2 服务端向客户端传递字符串

public class Server1 {public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(8088);Socket socket = serverSocket.accept();OutputStream outputStream = socket.getOutputStream();outputStream.write("来自服务端".getBytes());outputStream.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
public class Client1 {public static void main(String[] args) {try {Socket socket = new Socket("localhost", 8088);char[] charBuffer = new char[3];InputStream inputStream = socket.getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream);int readLength = inputStreamReader.read(charBuffer);while (readLength != -1) {System.out.println(new String(charBuffer, 0, readLength));readLength = inputStreamReader.read(charBuffer);}System.out.println();inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

在这里插入图片描述

1.3 实现服务端与客户端的连续多次的长连接通信

public class Server2 {public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(8088);Socket socket = serverSocket.accept();// 输入开始InputStream inputStream = socket.getInputStream();ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);int byteLength = objectInputStream.readInt();byte[] byteArray = new byte[byteLength];objectInputStream.readFully(byteArray);String newString = new String(byteArray);System.out.println(newString);// 输入结束// 输出开始OutputStream outputStream = socket.getOutputStream();String strA = "客户端你好A\n";String strB = "客户端你好B\n";String strC = "客户端你好C\n";int allStrByteLength = (strA + strB + strC).getBytes().length;ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeInt(allStrByteLength);objectOutputStream.flush();objectOutputStream.write(strA.getBytes());objectOutputStream.write(strB.getBytes());objectOutputStream.write(strC.getBytes());objectOutputStream.flush();// 输出结束// 输入开始byteLength = objectInputStream.readInt();byteArray = new byte[byteLength];objectInputStream.readFully(byteArray);newString = new String(byteArray);System.out.println(newString);// 输入结束// 输出开始strA = "客户端你好D\n";strB = "客户端你好E\n";strC = "客户端你好F\n";allStrByteLength = (strA + strB + strC).getBytes().length;objectOutputStream.writeInt(allStrByteLength);objectOutputStream.write(strA.getBytes());objectOutputStream.write(strB.getBytes());objectOutputStream.write(strC.getBytes());objectOutputStream.flush();// 输出结束inputStream.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
public class Client2 {public static void main(String[] args) {try {Socket socket = new Socket("localhost", 8088);OutputStream outputStream = socket.getOutputStream();InputStream inputStream = socket.getInputStream();// 输出开始ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);String strA = "服务端你好A\n";String strB = "服务端你好B\n";String strC = "服务端你好C\n";int allStrByteLength = (strA + strB + strC).getBytes().length;objectOutputStream.writeInt(allStrByteLength);objectOutputStream.flush();objectOutputStream.write(strA.getBytes());objectOutputStream.write(strB.getBytes());objectOutputStream.write(strC.getBytes());objectOutputStream.flush();// 输出结束// 输入开始ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);int byteLength = objectInputStream.readInt();byte[] byteArray = new byte[byteLength];objectInputStream.readFully(byteArray);String newString = new String(byteArray);System.out.println(newString);// 输入结束// 输出开始strA = "服务端你好D\n";strB = "服务端你好E\n";strC = "服务端你好F\n";allStrByteLength = (strA + strB + strC).getBytes().length;objectOutputStream.writeInt(allStrByteLength);objectOutputStream.flush();objectOutputStream.write(strA.getBytes());objectOutputStream.write(strB.getBytes());objectOutputStream.write(strC.getBytes());objectOutputStream.flush();// 输出结束// 输入开始byteLength = objectInputStream.readInt();byteArray = new byte[byteLength];objectInputStream.readFully(byteArray);newString = new String(byteArray);System.out.println(newString);// 输入结束objectOutputStream.close();outputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

在这里插入图片描述
在这里插入图片描述

1.4 使用Socket传递JPG图片文件

在这里插入图片描述
最近在看加缪的书,不得不说他的《西西弗神话》写得很动人,给我的震撼与思索比《局外人》要大。受到他的鼓舞我也从迷茫状态走出来了一点。

public class Server3 {public static void main(String[] args) {try {byte[] byteArray = new byte[2048];ServerSocket serverSocket = new ServerSocket(8088);Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();int readLength = inputStream.read(byteArray);FileOutputStream jpgOutputStream = new FileOutputStream(new File("E:\\IO\\new Albert Camus.jpg"));while (readLength != -1) {jpgOutputStream.write(byteArray, 0, readLength);readLength = inputStream.read(byteArray);}jpgOutputStream.close();inputStream.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
public class Client3 {public static void main(String[] args) {try {String jpgFilePath = "E:\\IO\\Albert Camus.jpg";FileInputStream jpgStream = new FileInputStream(new File(jpgFilePath));byte[] byteArray = new byte[2048];Socket socket = new Socket("localhost", 8088);OutputStream outputStream = socket.getOutputStream();int readLength = jpgStream.read(byteArray);while (readLength != -1) {outputStream.write(byteArray, 0, readLength);readLength = jpgStream.read(byteArray);}outputStream.close();jpgStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

在这里插入图片描述
如图,服务端收到了”加缪“,命名为”new Albert Camus“。
在这里插入图片描述
图像大小也完全一致。

1.5 结合多线程Thread实现通信

客户端每发起一次新的请求,就把这个请求交给新创建的线程来执行这次业务。当然,如果使用线程池技术,则会更加高效。

使用原始的非线程池

public class BeginServer {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8888);int runTag = 1;while (runTag == 1) {Socket socket = serverSocket.accept();BeginThread beginThread = new BeginThread(socket);beginThread.start();}serverSocket.close();}
}
public class BeginThread extends Thread {private Socket socket;public BeginThread(Socket socket) {super();this.socket = socket;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();InputStreamReader reader = new InputStreamReader(inputStream);char[] charArray = new char[1000];int readLength = -1;while ((readLength = reader.read(charArray)) != -1) {String newString = new String(charArray, 0, readLength);System.out.println(newString);}reader.close();inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
public class BeginClient {public static void main(String[] args) throws IOException {Socket socket = new Socket("localhost", 8888);OutputStream outputStream = socket.getOutputStream();outputStream.write("我来自客户端".getBytes());outputStream.close();socket.close();}
}

在这里插入图片描述
服务端与客户端成功进行通信,每个任务以异步的方式一起执行,大大增加程序运行时的吞吐量,提高了数据处理的能力。

使用线程池

public class Server4 {private ServerSocket serverSocket;private Executor pool;public Server4(int port, int poolSize) {try {serverSocket = new ServerSocket(port);pool = Executors.newFixedThreadPool(poolSize);} catch (IOException e) {e.printStackTrace();}}public void startService() {try {for (; ; ) {Socket socket = serverSocket.accept();pool.execute(new ReadRunnable(socket));}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws Exception{Server4 server = new Server4(8088, 10000);server.startService();}
}
public class ReadRunnable implements Runnable {private Socket socket;public ReadRunnable(Socket socket) {super();this.socket = socket;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();byte[] byteArray = new byte[100];int readLength = inputStream.read(byteArray);while (readLength != -1) {System.out.println(new String(byteArray, 0, readLength));readLength = inputStream.read(byteArray);}inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}

客户端不用改也行。

1.6 服务端与客户端互传对象以及I/O流顺序问题

本实验将实现Server与Client交换UserInfo对象,而不是前面String类型的数据。

public class Server {public static void main(String[] args) throws IOException, ClassNotFoundException {ServerSocket serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);for (int i = 0; i < 5; i++) {UserInfo userInfo = (UserInfo) objectInputStream.readObject();System.out.println("在服务端打印" + (i + 1) + ":" + userInfo.getId() + "-" + userInfo.getUsername() + "-" + userInfo.getPassword());UserInfo newUserInfo = new UserInfo();newUserInfo.setId(i + 1);newUserInfo.setUsername("serverUsername" + (i + 1));newUserInfo.setPassword("serverPassword" + (i + 1));objectOutputStream.writeObject(newUserInfo);}objectOutputStream.close();objectInputStream.close();outputStream.close();inputStream.close();socket.close();serverSocket.close();}
}
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {Socket socket = new Socket("localhost", 8888);InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);for (int i = 0; i < 5; i++) {UserInfo newUserInfo = new UserInfo();newUserInfo.setId(i + 1);newUserInfo.setUsername("clientUsername" + (i + 1));newUserInfo.setPassword("clientPassword" + (i + 1));objectOutputStream.writeObject(newUserInfo);UserInfo userInfo = (UserInfo) objectInputStream.readObject();System.out.println("在客户端打印" + (i + 1) + ":" + userInfo.getId() + "-" + userInfo.getUsername() + "-" + userInfo.getPassword());}objectOutputStream.close();objectInputStream.close();outputStream.close();inputStream.close();socket.close();}
}

为了避免在服务端阻塞,正确的写法应该是:

  1. 服务端先获得ObjectInputStream对象,客户端就要先获得ObjectOutputStream对象;
  2. 服务端先获得ObjectOutputStream对象,客户端就要鲜活的ObjectInputStream对象。

在这里插入图片描述
在这里插入图片描述

2.ServerSocket类的使用

ServerSocket类中有很多方法。熟悉常用的API对网络编程是很有好处的。

2.1 接受accept与超时Timeout

public Socket accept()方法的作用就是侦听并接受此套接字的连接。此方法在连接传入之前一直阻塞。
setSoTimeout (timeout)方法的作用是设置超时时间,通过指定超时timeout值启用/禁用SO_TIMEOUT,以ms为单位。在将此选项设为非零的超时timeout值时,对此ServerSocket调用accept()方法将只阻塞timeout的时间长度。如果超过超时值,将引发java.net. SocketTimeoutException,但ServerSocket仍旧有效,在结合try-catch结构后,还可以继续进行accept()方法的操作。SO_TIMEOUT选项必须在进入阻塞操作前被启用才能生效。注意,超时值必须是大于0的数。超时值为0被解释为无穷大超时值。参数int timeout的作用是在指定的时间内必须有客户端的连接请求,超过这个时间即出现异常,默认值是0,即永远等待。int getSoTimeout()方法的作用是获取SO_TIMEOUT的设置。返回0意味着禁用了选项(即无穷大的超时值)。

serverSocket.setSoTimeout(4000);

设置超时时间为4s,4s结束后仍没有客户端连接时服务端就会出现超时异常java.net.SocketTimeoutException: Accept timedout

2.2 构造方法的backlog参数含义

public ServerSocket(int port, int backlog)

backlog的主要作用就是允许接受客户端连接请求的个数。客户端有很多连接进入到操作系统中,将这些连接放入操作系统的队列中,当执行accept()方法时,允许客户端连接的个数要取决于backlog参数。

利用指定的backlog创建服务器套接字并将其绑定到指定的本地端口号port。对port端口参数传递值为0,意味着将自动分配空闲的端口号。

传入backlog参数的作用时设置最大等待队列长度,如果队列已满,则拒绝该连接。并报出异常:java.net.ConnectException:Conection refused:connect

backlog参数必须时大于0的正值,如果传递的值<=0,则使用默认值50。

2.3 构造方法ServerSocket(int port, int backlog, InetAddress bindAddr)的使用

public ServerSocket(int port, int backlog, InetAddress bindAddr)

使用指定的port和backlog将Socket绑定到本地InetAddress bindAddr来创建服务器。bindAddr参数可以在ServerSocket的多宿主主机(multi-homed host)上使用,ServerSocket仅接受对其多个地址的其中一个的连接请求。如果bindAddr为null,则默认接受任何/所有本地地址上的连接。注意,端口号必须0~65535(包括两者)。

多宿主主机代表一台计算机有两块网卡,每个网卡有不同的IP地址,也有可能出现一台计算机有1块网卡,但这块网卡有多个IP地址的情况。

1)使用构造方法public ServerSocket (int port)和publicServerSocket (int port, int backlog)创建ServerSocket对象,则客户端可以使用服务器任意的IP连接到ServerSocket对象中。

2)在使用public ServerSocket (int port, int backlog,InetAddress bindAddr)构造方法中的参数bindAddr创建ServerSocket对象后,客户端想要连接到服务端,则客户端Socket的构造方法的参数要写上与ServerSocket构造方法的参数bindAddr相同的IP地址,不然就会出现异常。

3.Socket类的使用

3.1 获得远程端口与本地端口

public int getPort()方法的作用是返回此套接字连接到的远程端口。
public int getLocalPort()方法的作用是返回此套接字绑定到的本地端口。

服务端的输出和客户端的输出在这个内容是相反的。

4.基于UDP的Socket通信

TCP中包含了专门的传递保证机制,来确保发送的数据能到达对端,并且时有序的。UDP与TCP不同,UDP并不提供数据传送的保证机制,如果在从发送方到接收方的传递过程中出现数据包的丢失,协议本身并不能做出任何监测或提示,因此,经常把UDP称为不可靠的传输协议。

另外,相对于TCP,UDP的另外一个不同之处是不能确保数据的发送和接受的顺序。例如,客户端的应用程序向服务端发送了以下4个数据包:

D1
D22
D333
D4444

但是UDP有可能按照以下的顺序接受数据:

D333
D1
D4444
D22

但某些情况下,UDP可能会变得非常有用,因为UDP具有TCP所不具有的速度上的优势。

使用UDP实现Socket通信时,服务端与客户端都是使用DatagramSocket类,传输的数据要存放在Datagrampacket类中。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部