java socket多线程编程

服务端ServerSocket和客户端Socket之间通过Socket建立连接和通信。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。
服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。
为了实现多线程,ServerSocket采用while(true)语句,调用其accept方法试图接收来自客户端的连接请求。当没有接收到请求的时候,程序会在这里阻塞直到接收到来自客户端的连接请求,之后会跟当前建立好连接的客户端进行通信,完了后会接着执行循环体再次尝试接收新的连接请求。每次ServerSocket接收到一个新的Socket连接请求后都会新起一个线程来跟当前Socket进行通信,这样就达到了异步处理与客户端Socket进行通信的情况。
BufferedReader的readLine方法是一次读一行的,这个方法是阻塞的,直到它读到了一行数据为止程序才会继续往下执行,那么readLine什么时候才会读到一行呢?直到程序遇到了换行符或者是对应流的结束符readLine方法才会认为读到了一行,才会结束其阻塞,让程序继续往下执行。所以我们在使用BufferedReader的readLine读取数据的时候一定要记得在对应的输出流里面一定要写入换行符(流结束之后会自动标记为结束,readLine可以识别),写入换行符之后一定记得如果输出流不是马上关闭的情况下记得flush一下,这样数据才会真正的从缓冲区里面写入。
在服务端代码中我们在定义输入流的时候明确定义了使用GBK编码来读取数据,而在定义输出流的时候明确指定了将使用UTF-8编码来发送数据。如果客户端上送数据的时候不以GBK编码来发送的话服务端接收的数据就很有可能会乱码;同样如果客户端接收数据的时候不以服务端发送数据的编码,即UTF-8编码来接收数据的话也极有可能会出现数据乱码的情况。所以要保持发送和接收的数据一致性。

Client.java:

package com.sun;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.net.SocketTimeoutException;


public class Client {

    public static void main(String args[]) throws Exception {//异常判断是主要为了确定是否能连接上,比如当端口号被占用时应当抛出异常
        String host = "127.0.0.1";  //要连接的服务端IP地址,这里为了便于测试,选择本地IP
        int port = 9000;   //要连接的服务端对应的监听端口,可任意选择
        Socket client = new Socket(host, port);        //与服务端建立连接
        Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");  //建立连接后就可以往服务端写数据,设置数据格式为"GBK"
        writer.write("Hello,Server.");//写入要传输给服务器的数据
        writer.write("eof\n");//每次用eof作为结束标志符
        writer.flush();//记住要flush一下,只有这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待。

        BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));    //读服务端的数据,并设置编码格式为"UTF-8"
        int timeout = 10;
        client.setSoTimeout(timeout * 1000);//Socket为我们提供了一个setSoTimeout()方法来设置接收数据的超时时间,单位是毫秒。
        StringBuffer sb = new StringBuffer();//StringBuffer用来连接收到的字符串
        String temp;//BufferedReader提供了缓存,使用readLine来一次读一行,提高了读的效率,temp用来记录每一行的内容
        int index;//判断是否读到最后
        try {
            while ((temp = br.readLine()) != null) {
                if ((index = temp.indexOf("eof")) != -1) {//若读到最后,记录下后就break
                    sb.append(temp.substring(0, index));
                    break;
                }
                sb.append(temp);//连接字符串
            }
        } catch (SocketTimeoutException e) {
            System.out.println("time out");//因为设置了超时时间,当超时时需抛出异常
        }
        System.out.println("server: " + sb);
        writer.close();//close的顺序要注意,必须从后往前关闭
        br.close();
        client.close();
    }
}

Server.java:

package com.sun;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String args[]) throws IOException {//异常判断是主要为了确定是否能连接上,比如当端口号被占用时应当抛出异常
        int port = 9000;//定义一个ServerSocket监听在端口9000上
        ServerSocket server = new ServerSocket(port);//创建一个端口号为9000的ServerSocket
        while (true) {//服务端一直保持监听状态
            Socket socket = server.accept();//server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
            new Thread(new Task(socket)).start(); //每接收到一个Socket就建立一个新的线程,并启动该线程
        }
    }

    /**
     * Task:处理接受到Socket请求的类
     */
    static class Task implements Runnable {//Task类实现Runnnable接口

        private Socket socket;//拥有Socket成员

        public Task(Socket socket) {
            this.socket = socket;
        }

        public void run() {
            try {
                handleSocket();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         * 跟客户端Socket进行通信
         *
         * @throws Exception
         */
        private void handleSocket() throws Exception {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));//读客户端的数据,并设置编码格式为"GBK"
            StringBuilder sb = new StringBuilder();
            String temp;
            int index;
            while ((temp = br.readLine()) != null) {
                System.out.println(temp);
                if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
                    sb.append(temp.substring(0, index));
                    break;
                }
                sb.append(temp);
            }
            System.out.println("Client: " + sb);

            Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
            writer.write("Hello,Client");
            writer.write("eof\n");
            writer.flush();
            writer.close();
            br.close();
            socket.close();//socket别忘了关闭
        }
    }
}

在命令行中,用javac进行编译生成两个class文件
注意:在Java Server或Java Client时,因为上述代码中加入了包package com.sun,所以cd 要退回到com的上一级目录,用全称java com.sun.Server和java com.sun.Client才可以,否则会报错:找不到或无法加载主类包名。
保证port不被占用,Server程序打开一个
这里写图片描述

Client程序打开多个
这里写图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值