本文最后更新于:2024年3月18日 凌晨
Java Socket
Java提供了Socket类和ServerSocket类分别用于Client端的Server端的Socket通信编程,可将联网的任何两台计算机进行Socket通信,一台作为服务器,另一台作为客户端,也可以用一台计算机上运行的两个进程分别运行服务端和客户端程序。
Socket类
Socket类用在客户端,通过构造一个Socket类来建立与服务器的连接,Socket连接可以是流连接,也可以是数据报连接,这取决于构造Socket类时使用的构造方法,一般使用流连接,流连接的优点是所有数据都能准确,有序地送到接收方,缺点是速度较慢,Socket类的构造方法有如下4种:
Socket(String host,int port)
:构造一个连接指定主机,指定端口的流Socket
Socket(String host,int port,boolean kind)
:构造一个连接指定主机,指定端口的Socket类,boolean类型的参数用来设置是流Socket还是数据报Socket
Socket(InetAddress address,int port)
:构造一个连接指定Internet地址,指定端口的流Socket
Socket(InetAddress address,int port,boolean kind)
:构造一个连接指定Internet地址,指定端口的Socket类,boolean类型的参数用来设置是流Socket还是数据报Socket
在构造完Socket类后,就可以通过Socket类来建立输入,输出流,通过流来传送数据。
ServerSocket类
ServerSocket类用在服务器端,常用的构造方法有两种:
ServerSocket(int port)
:在制定端口上构造一个ServerSocket类。
ServerSocket(int port ,int queue length)
:在指定端口上构造一个ServerSocket类,第2个参数queueLength用于限制并发等待连接的客户最大数目。
建立连接与数据通信
Socket通信的基本过程。
在服务器端创建一个ServerSocket对象,通过执行accept()
方法监听客户连接,这将使线程处于等待状态。
在客户端建立Socket类,与某服务器的指定端口进行连接,服务a器监听到连接请求后,就可在两者之间建立连接。
连接建立之后,就可以取得相应的输入/输出流进行通信,一方的输出流发送的数据将被另一方的输入流读取。
[例16-1] :下面是一个简单的Socket通信演示程序。
客户方程序
1 2 3 4 5 6 7 8 9 10 11 class Client { public static void main (String[] args) throws IOException { Socket socket = new Socket("localhost" ,5432 ); InputStream sln = socket.getInputStream(); DataInputStream dis = new DataInputStream(sln); String message = dis.readUTF(); System.out.println(message); s.close(); } }
这里客户机要访问的计算机为本地主机(localhost),也就是在一台计算机上自己与自己通信,客户通过创建Socket与服务端建立连接后,可以取得Socket的输入流,用过滤流DataInputStream
的readUTF()
方法读取来自服务方的字符串,最后关闭Socket连接。
服务方程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Server { public static void main (String[] args) { try { ServerSocket socket = new ServerSocket(5423 ); while (true ){ Socket s1 = socket.accept(); OutputStream s1out = s1.getOutputStream(); DataOutputStream dos = new DataOutputStream(s1out); dos.writeUTF("Hello World!" ); System.out.println("a client is connected..." ); s1.close(); } }catch (IOException e){ } } }
通过accpet()
方法等待客户连接,如果无客户连接,线程将进入阻塞状态,一旦有客户连接成功,则将在客户与服务器间建立一条Socket数据传输通道
通过Socket的getOutputStream()
方法可获得该通道本方的输出流,为了方便流操作,可以用DataOutputStream流对其进行过滤,并用DataOutputStream
对象的writeUTF()
方法给客户发送数据,而后关闭与该客户的连接,继续循环等待其他客户的访问。
注意 :该程序在同一机器上运行时要开辟两个DOS窗口,首先运行服务器程序,然后在另一个窗口运行客户程序,服务器端循环运行等待客户连接,每个客户连接到服务器后,在客户方将显示服务器发送的信息"Hello World!“,而服务方将显示”a client is connected…”,本程序中只是服务方给客户方发送数据,如果客户要给服务方发送数据,方法一样,只是要注意双方收发的配合。
思考 :上面的程序如果要实现双向通行,则服务器需要获取客户发送的数据,如果客户连接和读取客户数据安排在一个循环中,那么等待读取客户发送的数据将导致线程阻塞,不能及时转向执行accept()
方法去等待其他客户连接,因此,对于复杂的多用户通信是不可行的。
实例
[例16-2] :一个简单的多用户聊天程序
聊天客户端程序
聊天客户端的职责有两个:
能提供一个图形界面实现聊天信息的输入和显示,其中包括处理用户输入时间。
要随时接受来自其他客户的信息并显示出来,因此在客户端也采用多线程实现,应用程序主线程负责图形界面的输入处理,而接受消息线程负责读取其他客户发来的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class TalkClient { public static void main (String[] args) throws IOException { Socket s1 = new Socket(args[0 ], 5432 ); DataInputStream dis = new DataInputStream(s1.getInputStream()); final DataOutputStream dos = new DataOutputStream(s1.getOutputStream()); Frame myframe = new Frame("简易聊天室" ); Panel panelx = new Panel(); final TextField input = new TextField(20 ); TextArea display = new TextArea(5 , 20 ); panelx.add(input); panelx.add(display); myframe.add(panelx); new receiveThread(dis, display); input.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent e) { try { dos.writeUTF(input.getText()); } catch (IOException z) { } } }); myframe.setSize(300 , 300 ); myframe.setVisible(true ); } }class receiveThread extends Thread { DataInputStream dis; TextArea displayarea; public receiveThread (DataInputStream dis, TextArea m) { this .dis = dis; displayarea = m; this .start(); } public void run () { for (; ; ) { try { String str = dis.readUTF(); displayarea.append(str + "\n" ); } catch (IOException e) { } } } }
聊天服务端程序
聊天服务端的任务主要有两个。
监听某端口,建立与客户的Socket连接,处理一个客户的连接后,能很快再进入监听状态。
处理与客户的通信,由于聊天是在客户之间进行的,所以服务器的职责是将客户发送的消息转发给其他客户,为了实现这两个任务,必须设法将人物分开,可以借助多线程技术,在服务方为每个客户连接建立一个通信线程,通信线程负责接收客户的消息并将消息转发给其他客户,这样主程序的任务就简单化了,循环监听客户连接,每个客户连接成功后,创建一个通信线程,并将与Socket对应的输入/输出流传给该线程。
此例中还有一个关键问题是,由于要将数据转发给其他客户,因此某个客户对应的通信线程要设法获取其他客户的Socket输出流,也就是必须设法将所有客户的资料在连接处理时保存在一个公共能访问的地方,因此,在TalkServer类中引入了一个静态ArrayList方法存放所有客户的通信线程,这样要取得其他客户相关的输出流可通过该ArrayList方法去间接访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class TalkServer { public static ArrayList<Client> allclient = new ArrayList<Client>(); public static int clientnum = 0 ; public static void main (String[] args) { try { ServerSocket s = new ServerSocket(5432 ); while (true ) { Socket s1 = s.accept(); DataOutputStream dos = new DataOutputStream(s1.getOutputStream()); DataInputStream din = new DataInputStream(s1.getInputStream()); Client x = new Client(clientnum, dos, din); allclient.add(x); x.start(); clientnum++; } } catch (IOException e) { } } }class Client extends Thread { int id; DataOutputStream dos; DataInputStream din; public Client (int id, DataOutputStream dos, DataInputStream din) { this .id = id; this .dos = dos; this .din = din; } public void run () { while (true ) { try { String message = "客户" + id + ":" + din.readUTF(); for (int i = 0 ; i < TalkServer.clientnum; i++) { TalkServer.allclient.get(i).dos.writeUTF(message); } } catch (IOException e) { } } } }
运行该程序前首先要运行服务方程序,运行客户方程序要注意提供一个代表服务器地址的参数,如果客户方程序与服务方程序在同一机器上运行,则客户方运行命令为:
1 java TalkClient localhost
思考
该程序仅实现了简单的多用户聊天演示,在程序中还有许多问题值得改进,例如:
如何修改服务方,使用户自己发送的消息不显示在自己的文本域中。
增加一个用户名输入界面,用户输入身份后再进入聊天界面。
在客户方显示用户列表,可以选择将信息发送给哪些用户。
如何在服务方对退出的用户进行处理,保证聊天发送的消息只发给在场的用户,这点要客户方与服务方配合编程,客户退出时给服务方发消息,或者给服务器设置一个监视线程,检查各通信线程的Socket通道是否正常,对于不正常的通道自动停止相关的通信线程。
TCP文件上传实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Client { public static void main (String[] args) throws IOException { Socket socket = new Socket(InetAddress.getByName("127.0.0.1" ), 9000 ); OutputStream outputStream = socket.getOutputStream(); FileInputStream fileInputStream = new FileInputStream("test.txt" ); byte [] buffer = new byte [1024 ]; int length; while ((length = fileInputStream.read(buffer)) != -1 ) { outputStream.write(buffer, 0 , length); } socket.shutdownOutput(); InputStream inputStream = socket.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte [] buffer2 = new byte [1024 ]; int length2; while ((length2 = inputStream.read(buffer2)) != -1 ) { byteArrayOutputStream.write(buffer2, 0 , length2); } System.out.println(byteArrayOutputStream); fileInputStream.close(); outputStream.close(); socket.close(); } }class Server { public static void main (String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000 ); Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); FileOutputStream fileOutputStream = new FileOutputStream(new File("receive.txt" )); byte [] buffer = new byte [1024 ]; int length; while ((length = inputStream.read(buffer)) != -1 ) { fileOutputStream.write(buffer, 0 , length); } OutputStream os = socket.getOutputStream(); os.write("Receive successful!" .getBytes()); fileOutputStream.close(); inputStream.close(); socket.close(); serverSocket.close(); } }