Java Socket网络编程
计算机网络中,客户机与服务器之间进行通信时,客户机必须知道服务器的IP地址和端口号。IP地址是标识Internet网络中的某台计算机,而端口号则标识着在服务器上运行的某个程序(进程),如果在服务器上运行的程序,没有端口号,则客户端的程序就不能找到它,也不能和它进行通信。一定要清楚,别和电脑上的物理端口号搞混了。 端口号是一个逻辑的地址,用于标识计算机中运行着的应用程序。它可以是0~65535之间的一个数,0~1023为系统保留使用,例如http的是80端口等,这些是系统已经分配给默认服务使用的。 当两个应用程序需要通过网络通信时,就可以使用IP地址和端口号进行通过,IP地址和端口号组合在一起就是一个Socket(网络套接字)。套接字分为服务端的和客户端的两种
一、客户端套接字
客户端的套接字用于与服务器端的套接字进行连接。其使用方法如下:
socket=new Socket("127.0.0.1",2018);
其中127.0.0.1标识服务器端的IP地址,2018标识服务器端应用程序的端口号。在建立套接字的过程中,可能会发生IOException异常,要注意进行处理。
二、服务器端套接字
客户端与服务器端通信时,服务器端负责提供服务,客户端通过Socket向服务器端请求服务。因此,在服务器端也必须有相应的套接字与客户端进行连接。使用如下:
try {
serverSocket=new ServerSocket(2018);
socket=serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
ServerSocket的构造方法中,只有一个参数,即Port号,需要注意的是,服务器端的Port号必须与客户端的相同。
当调用ServerSocket的accept()方法时,会返回一个客户端套接字对象,即Socket对象。该对象在客户端与服务端连接的过程中,将驻留在服务器的内存中,我们通过它就可以与客户端进行数据的传输。
三、客户端与服务端通信的简单实现
客户端与服务端通信时,首先服务端要在相应的端口进行监听,等待客户端的连接请求。然后在客户端请求时,接受客户端的请求,然后进行通信。首先是服务端。代码如下:
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class MyServerFrame extends JFrame{ //接收客户端发送的消息 private JTextArea allMessage=new JTextArea(8,20); // 输入发送给客户端的消息 private JTextField sendMessage=new JTextField(20); //消息发送按钮 private JButton sendBtn=new JButton("发送"); private JButton startServer=new JButton("开始服务器"); private ServerSocket serverSocket=null;//服务器端Socket private BufferedReader in=null;//用于接收客户端发送的数据 private BufferedWriter out=null;//用于发送数据给客户端 private Socket socket=null;//客户端连接对象 private int port; public void setPort(int port){ this.port=port; } public MyServerFrame(){ init(); } private void init(){ setTitle("服务端"); setLayout(new BorderLayout()); //开始服务器按钮单击事件 startServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { serverSocket=new ServerSocket(port); socket=serverSocket.accept(); new receiverMsgThread().start();//启动线程接收客户端数据 } catch (IOException e1) { e1.printStackTrace(); } } }); //发送按钮单击事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); } catch (IOException e1) { e1.printStackTrace(); } } }); //将组件添加到窗体中 JPanel panel=new JPanel(); panel.add(startServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗体 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } //接收信息线程 class receiverMsgThread extends Thread{ public void run(){ try { in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String str=null; while (true){ str=in.readLine()+"\n"; allMessage.append("客户端说:"+str); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyServerFrame myServerFrame=new MyServerFrame(); myServerFrame.setPort(12345); } }
客户端代码如下:
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.Socket; public class MyClientFrame extends JFrame{ //接收客户端发送的消息 private JTextArea allMessage=new JTextArea(8,20); // 输入发送给客户端的消息 private JTextField sendMessage=new JTextField(20); //消息发送按钮 private JButton sendBtn=new JButton("发送"); private JButton connServer=new JButton("连接服务器"); private Socket socket=null; private BufferedReader in=null; private BufferedWriter out=null; public MyClientFrame(){ init(); } private void init(){ setTitle("客户端"); setLayout(new BorderLayout()); //连接服务器按钮单击事件 connServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { socket=new Socket("127.0.0.1",12345); System.out.println("连接成功"); new receiverMsgThread().start(); } catch (IOException e1) { e1.printStackTrace(); } } }); //发送按钮单击事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); System.out.println("222"); } catch (IOException e1) { e1.printStackTrace(); } } }); //将组件添加到窗体中 JPanel panel=new JPanel(); panel.add(connServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗体 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } class receiverMsgThread extends Thread{ public void run(){ InputStream is=null; BufferedReader br=null; String str=null; try { is=socket.getInputStream(); br=new BufferedReader(new InputStreamReader(is)); while(true){ str=br.readLine()+"\n"; allMessage.append("服务器说:"+str); } } catch (IOException e) { e.printStackTrace(); } System.out.println("客户端"); } } public static void main(String[] args) { new MyClientFrame(); } }
程序运行界面如下:
目前为止,服务端只能与一个客户端进行通信。在编写的过程中,由于对BufferedReader类,之前并不了解,因此在编写的过程中,遇到了很大的问题,导致走了很多的弯路。最后通过查阅其文档,解决了问题。主要是因为其readLine()方法,在发送数据时,它是以回车符作为结束的,否则它不会结束,从而导致后续的操作无法完成,因此在发送数据时,一定要注意,代码如下:
out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); out.write(sendMessage.getText());//这个只是将数据写入到缓存中,并不真正进行发送 out.newLine();//这是一个新行,标识传输数据的结束,这点一定要注意 out.flush();//这个是将缓存中的数据进行发送
四、服务端的改进
以上程序代码中,服务器只能接收一个客户端的连接,下面对服务器端进行改进,实现多个客户端的连接。
思想如下:采用多线程方式,每一个客户端连接时,启动一个线程负责该客户端的通信,将产生的客户端Socket放入到一个集合中,然后,当客户端发送数据时,服务器端将收到的数据转发给所有的客户端。服务端修改后的代码如下;
package socket.demo2; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class MyServerFrame extends JFrame{ //保存客户端的Socket,用于向所有的客户端进行数据的转发。 private static List<Socket> clients=new ArrayList<Socket>(); //接收客户端发送的消息 private JTextArea allMessage=new JTextArea(8,20); // 输入发送给客户端的消息 private JTextField sendMessage=new JTextField(20); //消息发送按钮 private JButton sendBtn=new JButton("发送"); private JButton startServer=new JButton("开始服务器"); private ServerSocket serverSocket=null;//服务器端Socket private BufferedReader in=null;//用于接收客户端发送的数据 private BufferedWriter out=null;//用于发送数据给客户端 private Socket socket=null;//客户端连接对象 private int port; public void setPort(int port){ this.port=port; } public MyServerFrame(){ init(); } private void init(){ setTitle("服务端"); setLayout(new BorderLayout()); //开始服务器按钮单击事件 startServer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startServer.setEnabled(false); //启动连接线程 try { serverSocket=new ServerSocket(port); } catch (IOException e1) { e1.printStackTrace(); } new connClient().start(); } }); //发送按钮单击事件 sendBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { for(Socket s:clients){ out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); out.write(sendMessage.getText()); out.newLine(); out.flush(); } } catch (IOException e1) { e1.printStackTrace(); } } }); //将组件添加到窗体中 JPanel panel=new JPanel(); panel.add(startServer); panel.add(sendMessage); panel.add(sendBtn); add(allMessage,BorderLayout.CENTER); add(panel,BorderLayout.SOUTH); //配置窗体 setSize(500,300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } class connClient extends Thread{ public void run(){ while(true){ try { socket=serverSocket.accept(); clients.add(socket); new sendMsgToClients(socket).start(); new receiverMsgThread().start();//启动线程接收客户端数据 } catch (IOException e1) { e1.printStackTrace(); } } } } //接收信息线程 class receiverMsgThread extends Thread{ public void run(){ try { in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String str=null; while (true){ str=in.readLine()+"\n"; allMessage.append("客户端说:"+str); } } catch (IOException e) { e.printStackTrace(); } } } class sendMsgToClients extends Thread{ private Socket clientSocket; public sendMsgToClients(Socket clientSocket){ this.clientSocket=clientSocket; } public void run(){ //首先接收客户端发送过来的信息, //然后向客户端,进行信息的转发。 try { while(true){ in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String str=in.readLine(); for(Socket s:clients){ out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); out.write(str); out.newLine(); out.flush(); } } } catch (IOException e) { e.printStackTrace(); clients.remove(socket); } } } public static void main(String[] args) { MyServerFrame myServerFrame=new MyServerFrame(); myServerFrame.setPort(12345); } }