1.TCP 与 UDP 协议都在套接字编程中的区别
(1)UDP
数据报套接字
只需一个接收函数 recvfrom()和一个发送函数 sendto()
(2)TCP
流式套接字
服务器监听函数 listen()
客户端连接函数 connect()
服务器接收函数 accept()
发送函数 send()和接收函数 recv()
2.原理
(1)服务器端
(1)创建ServerSocket对象,绑定监听端口;
(2) 通过accept()方法监听客户端请求;
(3) 连接建立后,通过输入流读取客户端发送的请求信息;
(4) 通过输出流向客户端发送相应信息;
(5) 关闭响应资源
(2)客户端
(1) 创建Socket对象,指明需要连接的服务器地址和端口;
(2) 连接建立后,通过输出流向服务器端发送请求信息;
(3) 通过输入流获取服务器端返回的响应信息;
(4) 关闭响应资源。
3.代码编写
(1)客户端
1.首先,初始化Java的JFrame()窗口,形成图形化的界面;
public static void init(){
JFrame frame = new JFrame("TCP客户端");
frame.setBounds(100, 100, 753, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
JLabel lblip = new JLabel("我的名字:");
lblip.setBounds(56, 72, 93, 24);
frame.getContentPane().add(lblip);
JLabel label = new JLabel("服务器IP:");
label.setBounds(56, 24, 93, 24);
frame.getContentPane().add(label);
JLabel label_1 = new JLabel("端口号:");
label_1.setBounds(486, 24, 70, 24);
frame.getContentPane().add(label_1);
text_ip = new JTextField("127.0.0.1");
text_ip.setBounds(139, 24, 232, 30);
frame.getContentPane().add(text_ip);
text_ip.setColumns(10);
text_name = new JTextField();
text_name.setColumns(10);
text_name.setBounds(139, 69, 232, 30);
frame.getContentPane().add(text_name);
text_port = new JTextField();
text_port.setColumns(10);
text_port.setBounds(551, 21, 106, 30);
frame.getContentPane().add(text_port);
JButton button_login = new JButton("连接服务器");
button_login.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
try {
int port=Integer.parseInt(text_port.getText());
System.out.println("连接服务器!");
sc=new Socket("localhost",port); //绑定服务端地址与端口号
System.out.println("连接服务器成功!");
new Thread(new Runnable(){
public void run(){
try{
inputStreamReader=new InputStreamReader(sc.getInputStream());//提高效率,将自己字节流转为字符流
bufferedReader=new BufferedReader(inputStreamReader);//加入缓冲区
String info=null;
while(true){
info=bufferedReader.readLine();
System.out.println("接收到信息为:"+info);
textArea.append(info+"\n");
}
}
catch(IOException e){
textArea.append("服务器断开\n");
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
});
button_login.setBounds(496, 67, 161, 35);
frame.getContentPane().add(button_login);
textArea= new JTextArea(10,15);
textArea.setLineWrap(true);
textArea.setBounds(66, 119, 588, 232);
// JScrollPane jsp = new JScrollPane(textArea);//给文本区添加滚动条
frame.getContentPane().add(textArea);
JTextField text_send = new JTextField();
text_send.setBounds(67, 379, 419, 40);
frame.getContentPane().add(text_send);
text_send.setColumns(10);
JButton button_send = new JButton("发送");
button_send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
String message=text_name.getText()+":"+text_send.getText()+"\n"+"END";
String mes=text_name.getText()+":"+text_send.getText()+"\n";
textArea.append(mes);
send(message);
text_send.setText("");
}
});
button_send.setBounds(542, 379, 115, 40);
frame.getContentPane().add(button_send);
frame.setVisible(true);
}
2.连接绑定服务器,连接服务器的9999端口,用socket(“localhost”,port)进行建立与服务器9999端口的套接字连接;
int port=Integer.parseInt(text_port.getText());
System.out.println("连接服务器!");
sc=new Socket("localhost",port); //绑定服务端地址与端口号
System.out.println("连接服务器成功!");
3.当连接到服务器时,打开线程Thread(),读取服务器发过来的数据,用InputStreamReader(sc.getInputStream)将字节流转换为字符流,并且将字符流存入到BufferedReader缓冲区中,while(true)进行循环不断的接收服务器发送过来的信息,用bufferReader.readLine()去一行一行的获取数据,并且将数据显示在文本框中;
new Thread(new Runnable(){
public void run(){
try{
inputStreamReader=new InputStreamReader(sc.getInputStream());//提高效率,将自己字节流转为字符流
bufferedReader=new BufferedReader(inputStreamReader);//加入缓冲区
String info=null;
while(true){
info=bufferedReader.readLine();
System.out.println("接收到信息为:"+info);
textArea.append(info+"\n");
}
}
catch(IOException e){
textArea.append("服务器断开\n");
}
}
}).start();
4.当点击发送时,在发送按钮上用addActionListener监听事件,当点击发送时,获取文本框中的信息和发送人的姓名;并且在自己的文本框中显示,调用send()函数进行信息的发送,当发送成功后将发送信息框设置为空;
button_send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
String message=text_name.getText()+":"+text_send.getText()+"\n"+"END";
String mes=text_name.getText()+":"+text_send.getText()+"\n";
textArea.append(mes);
send(message);
text_send.setText("");
}
});
5.发送信息的函数,send()函数进行信息的发送,用PrintWriter获取写数据getOutputStream()去获取输出数据流;将消息打印输出,并且用pw.flush()将缓存发送出去;
public static void send(String mesg){
PrintWriter pw;
try {
pw = new PrintWriter(sc.getOutputStream());
pw.println(mesg);
pw.flush();
} catch (IOException e) {
System.out.println("客户端发送信息失败!");
e.printStackTrace();
}
}
ServerThread类
多线程类的实现,处理客户端发送来的数据,并且对客户端的返回数据信息;
1.首先初始化这个线程类,初始化并且接收传递过来的sc(socket连接信息),cs(连接本服务器的客户端的列表);
public ServerThread(Socket sc,Vector<Socket> cs){
this.sc=sc;
this.cs=cs; }
2.获取输入流,用sc.InputStream()获取到输入的字节流,将字节流转换为字符流InputStreamReader,并且将字符流放入到缓冲区BufferReader当中,用while循环一直等待输入端的输入缓冲区,并且用readLine()函数去读这个缓冲区的数据,当读到结束符“END”时停止;将信息放在info中存储;
is=sc.getInputStream(); //获取输入的字节流
isr=new InputStreamReader(is); //转换为字符流
br=new BufferedReader(isr); //放入到缓冲区
String info=null;
String temp=null;
while (!"END".equals(temp = br.readLine())) {
info=temp;
System.out.println("info:"+info);
System.out.println("已接收到客户端连接!");
System.out.println("服务端收到客户端信息:"+info+" 当前客户端的IP为:"+sc.getInetAddress().getHostAddress()+" 端口号为:"+sc.getPort());
}
sc.shutdownInput();
3.输出流转发信息到所有的客户端系统,对客户端列表的所有客户端进行遍历,发送给不是本客户端端口的所有其他客户端,用PrintWriter去输出数据,获取客户端的输出数据流soc.getOutputStream(),打印Info信息给输出端,调用flush()方法将缓冲输出;
for(Socket soc : cs){ //发送给已连接的所有客户端
if(!this.sc.equals(soc)){
pw=new PrintWriter(soc.getOutputStream());
pw.println(info);
pw.flush(); //调用flush()方法将缓冲输出
System.out.println("发送成功,发送给"+soc.getPort()+" "+info);
}
}
} catch (Exception e) {
System.out.println("线程已关闭!");
}
(2)服务器端
1.对服务器进行连接,对按钮增加监听,当按下按键时连接端口,对服务器进行连接,绑定地址和端口号,主机采用本地服务器127.0.0.1进行连接;这里绑定9999端口作为服务器端口号;ServerSocket ssc=new ServerSocket(9999); 创建一个服务器9999端口接收信息;
2.监听服务器端口,一直监听是否有连接到9999端口的客户端,用accept()方法进行监听,当没有客户端连接时,会一直处于阻塞的状态;当存在一个客户端的连接时,将客户端加入到 之前初始化的static public Vector<Socket> cs=new Vector<Socket>();序列中,并count++记录当前连接的客户端的数量,打印出当前客户端的IP号和端口号;
while(true){
sc=ssc.accept(); //accept()方法是用于监听接收客户端的连接
//创建新的线程
cs.add(sc);
ServerThread st=new ServerThread(sc,cs); //启动线程
st.start();
count++; //统计当前连接的客户端的数量
System.out.println("客户端数量:"+count);
InetAddress addr=sc.getInetAddress();
System.out.println("当前客户端的IP:"+addr.getHostAddress()+" 端口号:"+sc.getPort());
}
} catch (IOException e) {
System.out.println("TCP服务器启动失败!");
e.printStackTrace();
}
4.实验结果
(1)客户端进行连接服务器,输入端口号9999,点击连接服务器按键,可以看到控制台显示客户端数量与客户端的IP地址和端口号;以下也有客户端的显示界面如下所示:
(2)当有多个客户端进行连接的时候,服务端统计全部的客户端连接信息;并且初始化客户端的显示页面,在这里我们连接三个客户端;
3.现在开始进行信息的发送,可以看到其他客户端可以收到信息
4.其他客户端进行相应,并作出回复消息,可以看到基于tcp的多用户线程通信成功实现;