Java网络编程+多线程通信
最近在学习Java Socket通信,把一些学到的东西贴上来。
一、Socket概念
Socket作为BSD(Berkeley Software Distribution,伯克利软件套件) UNIX的进程通信机制,通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
二、利用Socket建立网络连接的步骤**
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。
而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
三、什么是TCP连接的三次握手
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
四、Java中的socket通信机制
Java的有联接(TCP)通信方式
Java提供的套接字分为客户端和服务器端两种:
客户端的socket
- Socket(String host,int port):生成一个连接到某个主机内某个端口号码的套接字。
- Socket(InetAddress host,int port):生成一个连接到某个主机地址上某个端口号码的套接字。
- InputStream getInputStream():得到套接字的输入数据流。
- OutputStream getOutputStream():得到套接字的输出数据流。
- 工作完毕,使用流对象的close()方法关闭用于网络通信的输入、输出流,用Socket对象的close()方法关闭socket。
服务器端的socket
- ServerSocket(int port):生成一个服务器套接字,并指定所使用的端口号码,在序列里等待连接的客户机的数目最大50,若端口的值为0,则使用任何可供使用的端口。
- ServerSocket(int port,int backlog):backlog表示在序列里等待连接的客户机的数目,若数目超过backlog,则客户机的连接被拒绝。
- InputStream getInputStream():得到套接字的输入数据流。
- OutputStream getOutputStream():得到套接字的输出数据流。
- Socket accept():等待接收来自客户的连接请求,服务器会等到联机为止。
- 工作完毕,使用流对象的close()方法关闭用于网络通信的输入、输出流,用ServerSocket对象的close()方法关闭监听socket。
五、多线程通信实例
TCPServer.java
package server;
import java.awt.Font;
import java.awt.GridLayout;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
public class TCPServer extends JFrame{
private ServerSocket server;
private Socket client;
private int port;
private static JTextField receive;
private static JTextField send;
private static Writer writer;
private static BufferedReader reader;
private static int count = 1;
public TCPServer(int port){
this.setTitle("Server");
receive = new JTextField();
send = new JTextField();
this.setVisible(true);
this.setBounds(400, 400, 600, 400);
this.setLayout(new GridLayout(2,2));
JLabel label1 = new JLabel("收到");
label1.setFont(new Font("Dialog", 1,35));
this.add(label1);
this.add(receive);
JLabel label2 = new JLabel("发送");
label2.setFont(new Font("Dialog", 1,35));
this.add(label2);
this.add(send);
validate();
this.port = port;
try {
server = new ServerSocket(port);
while(true){ //不断监听是否有客户端进程接入
client = server.accept();
Thread thread = new Thread(new task(client));
thread.setName("客户端线程"+count++);
thread.start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static class task implements Runnable{
Socket client;
task(Socket client){
this.client = client;
}
@Override
public void run() {
try {
handleSocket();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void handleSocket() throws Exception{
try {
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
while(true){
StringBuilder stringRead = new StringBuilder();
String temp;
int index;
while((temp = reader.readLine())!= null){
System.out.println(temp);
if((index=temp.indexOf("eof"))!=-1){
stringRead.append(temp.substring(0, index));
break;
}
stringRead.append(temp);
}
System.out.println(stringRead.toString());
receive.setText(stringRead.toString());
writer = new OutputStreamWriter(client.getOutputStream());
if(send.getText()==""){
writer.write(Thread.currentThread().getName());
}
else{
writer.write("SendTo客户端"+Thread.currentThread().getName()+":");
writer.write(send.getText());
System.out.println("Server:send to Client:"+send.getText());
}
writer.write("eof\n");
writer.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
TCPClient.java
package client;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TCPClient extends JFrame {
private int port;
private String host;
private static JTextField receive;
private static JTextField send;
private JButton commit;
private Socket client;
private Writer writer;
private BufferedReader reader;
private String clientID;
public TCPClient(int port,String host){
this.setTitle("Client");
// get name representing the running Java virtual machine.
String name = ManagementFactory.getRuntimeMXBean().getName();
// get pid
String clientID = name.split("@")[0];
this.host = host;
this.port = port;
this.setLayout(new GridLayout(3,1));
receive = new JTextField();
send = new JTextField();
commit = new JButton("提交");
JLabel label1 = new JLabel("收到");
label1.setFont(new Font("Dialog", 1,35));
JPanel panel1 = new JPanel();
panel1.setLayout(new GridLayout(1,2));
panel1.add(label1);
panel1.add(receive);
this.add(panel1);
JLabel label2 = new JLabel("发送");
label2.setFont(new Font("Dialog", 1,35));
JPanel panel2 = new JPanel();
panel2.setLayout(new GridLayout(1,2));
panel2.add(label2);
panel2.add(send);
this.add(panel2);
this.add(new JPanel().add(commit));
this.setVisible(true);
this.setBounds(500, 500, 600, 400);
this.validate();
CreateConnection(port,host);
commit.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
if(client.isConnected()){
String text = send.getText();
try {
writer.write("客户端进程"+clientID+":");
writer.write(text);
writer.write("eof\n");
writer.flush();
StringBuilder in = new StringBuilder();
String temp;
int index;
while ((temp=reader.readLine()) != null) {
if ((index = temp.indexOf("eof")) != -1) {
in.append(temp.substring(0, index));
break;
}
in.append(temp);
}
if(in.toString()!=null){
System.out.println("Client:receive from Server:"+in.toString());
receive.setText(in.toString());
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
}
void CreateConnection(int port,String host){
try {
client = new Socket(host,port);
writer = new OutputStreamWriter(client.getOutputStream());
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Main.java
package start;
import client.TCPClient;
import server.TCPServer;
public class Main {
static int port;
public static void main(String[] args){
port = 2500;
new TCPServer(port);
}
}
startClient.java
package start;
import client.TCPClient;
public class startClient {
static int port;
static String host;
public static void main(String[] args){
port = 2500;
host = "localhost";
new TCPClient(port,host);
}
}
功能:一个服务端同时连接多个客户端,实现并发通信,没有考虑线程安全。
我这里把客户端和服务端放在一个工程里面,在Eclipse中运行要进入到对应的main函数里。
建议把它们分别放到两个不同的工程里面。