<网络编程Ⅰ>Socket通信
参考书籍:
-
网络是怎样连接的

很有趣味的一本书,但也没那么容易读通。 -
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();}
}
为了避免在服务端阻塞,正确的写法应该是:
- 服务端先获得ObjectInputStream对象,客户端就要先获得ObjectOutputStream对象;
- 服务端先获得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类中。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
