Granda's Blog

JAVA:网络编程

TCP单线程(最简单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Server {
public static void main(String[] args) throws Exception{
//创建ServerSocket,监听30000端口的请求
ServerSocket ss = new ServerSocket(30000);
while(true){
//该方法会阻塞直到有Socket发起连接
Socket s = ss.accept();
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println("你好,这是服务器的回复");
ps.close();
s.close();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) throws Exception {
//创建Socket,绑定IP和端口
Socket s = new Socket("127.0.0.1", 30000);
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = br.readLine();
System.out.println("来自服务器:" + line);
br.close();
s.close();
}
}

TCP多线程

  • 服务器主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Server {
    //创建线程安全的ArrayList来保存与服务器连接的Socket
    public static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());
    public static void main(String[] args) throws Exception{
    //创建ServerSocket,监听30000端口的请求
    ServerSocket ss = new ServerSocket(20000);
    while(true){
    Socket s = ss.accept();
    socketList.add(s);
    //每个客户端新建一个服务线程
    new Thread(new ServerThread(s)).start();
    }
    }
    }
  • 服务器服务线程:负责将客户端送来的数据返回到和服务器连接的所有客户端

    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
    public class ServerThread implements Runnable {
    //定义当前处理的Socket
    Socket s = null;
    BufferedReader br = null;
    public ServerThread(Socket s) throws Exception{
    this.s = s;
    br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    }
    @Override
    public void run() {
    try{
    String content = null;
    while((content = readFromClient()) != null ){
    for(Socket s : Server.socketList){
    PrintStream ps = new PrintStream(s.getOutputStream());
    ps.println(content);
    }
    }
    }catch(IOException e){
    e.printStackTrace();
    }
    }
    private String readFromClient() {
    try{
    return br.readLine();
    }catch(IOException e){
    Server.socketList.remove(s);
    }
    return null;
    }
    }
  • 客户端主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Client {
    public static void main(String[] args) throws Exception {
    //创建Socket,绑定IP和端口
    Socket s = new Socket("127.0.0.1", 20000);
    //客户端启动线程不断读服务器的返回的数据
    new Thread(new ClientThread(s)).start();
    //获取Socket的输出流
    PrintStream ps = new PrintStream(s.getOutputStream());
    //获取键盘的输入流
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String line = null;
    while((line = br.readLine()) != null){
    //将键盘输入的数据输出到Socket
    ps.println(line);
    }
    }
    }
  • 客户端服务线程:不停读取服务器发来的数据,并打印到屏幕

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ClientThread implements Runnable {
    private Socket s;
    //Socket的输入流,接受来自服务器的数据
    BufferedReader br = null;
    public ClientThread(Socket s) throws Exception {
    this.s = s ;
    br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    }
    @Override
    public void run() {
    try{
    String content = null;
    while((content = br.readLine()) != null){
    System.out.println(content);
    }
    }catch(IOException e){
    e.printStackTrace();
    }
    }
    }

TCP基于NIO

  • 服务器端有ServerSocketChannel和SocketChannel。ServerSocketChannel负责用来监听客户端请求,SocketChannel负责和客户端传输数据,二者都需要注册到Selector中。
  • 客户端有SocketChannel,负责与服务器传输数据,也需要注册到Selector中
  • 创建一个ServerSocketChannel,并设置其为非阻塞模式,将其注册到指定Selector

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //通过open方法来打开一个未绑定的ServerSocketChannel实例
    ServerSocketChannel server = new ServerSocketChannel.open();
    //将ServerSocketChannel绑定到指定的IP地址
    InetSocketAddress isa = new InetSocketAddress("127.0.0.1",10000);
    server.bind(isa);
    //设置ServerSocketChannel以非阻塞模式工作
    server.configureBlocking(false);
    //将server注册到指定的Selector对象
    //之后可以调用Selecetor的select()方法来监听所有Channel
    server.register(selector , SelectionKey.OP_ACCEPT);
  • 编程实例 (服务器端)

    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
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public class NServer {
    private Selector selector = null;
    static final int PORT = 12000;
    //设置编译码的码字集,编译码的目的是将String转换成ByteBuffer,用于SocketChannel的传输
    private Charset charset = Charset.forName("UTF-8");
    private void init() throws IOException{
    //开启Selector
    selector = Selector.open();
    //开启ServerSocketChannel
    ServerSocketChannel server = ServerSocketChannel.open();
    //ServerSocketChannel绑定IP和端口
    InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);
    server.bind(isa);
    //设置ServerSocketChannel为非阻塞模式
    server.configureBlocking(false);
    //将ServerSocketChannel注册到Selector
    server.register(selector, SelectionKey.OP_ACCEPT);
    //select()方法是一个选择等待IO的Channel的阻塞方法,直到至少选中一个等待IO的Channel
    //返回的是选中Channel的数量,没有被选择器取消选中的Channel不可重新选中
    while(selector.select() > 0){
    //遍历选择器中选中的SelectionKey,每个Selection对应一个Channel
    for(SelectionKey sk : selector.selectedKeys()){
    //将SelectionKey取消选中
    selector.selectedKeys().remove(sk);
    //如果sk对应的Channel有连接请求
    if(sk.isAcceptable()){
    //创建与客户端通讯用的SocketChannel
    SocketChannel sc = server.accept();
    sc.configureBlocking(false);
    //将SocketChannel注册到Selector
    sc.register(selector, SelectionKey.OP_READ);
    //将sk对应的Channel设置成准备接受其它请求,如果有连接请求,该Channel就会被select()方法选中
    sk.interestOps(SelectionKey.OP_ACCEPT);
    }
    //如果sk对应的Channel有数据需要读取
    if(sk.isReadable()){
    SocketChannel sc = (SocketChannel)sk.channel();
    ByteBuffer buff = ByteBuffer.allocate(1024);
    String content = "";
    try{
    while(sc.read(buff) > 0){
    buff.flip();
    content += charset.decode(buff);
    }
    System.out.println("读取的数据:" + content);
    //将sk对应的Channel设置成准备接收数据,如果有数据传入,该Channel就会被select()方法选中
    sk.interestOps(SelectionKey.OP_READ);
    }catch(IOException e){
    sk.cancel();
    if(sk.channel() != null){
    sk.channel().close();
    }
    }
    if(content.length() > 0){
    //遍历选择器中的所有key
    for(SelectionKey key : selector.keys()){
    Channel targetChannel = key.channel();
    //如果key对应的Channel是SocketChannel的实例
    if(targetChannel instanceof SocketChannel){
    SocketChannel dest = (SocketChannel) targetChannel;
    dest.write(charset.encode(content));
    }
    }
    }
    }
    }
    }
    }
    public static void main(String[] args) throws IOException{
    new NServer().init();
    }
    }
  • 编程实例(客户端)

    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
    public class NClient {
    private Selector selector = null;
    static final int PORT = 12000;
    private Charset charset = Charset.forName("UTF-8");
    private SocketChannel sc = null;
    public void init() throws IOException{
    selector = Selector.open();
    InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);
    sc = SocketChannel.open(isa);
    sc.configureBlocking(false);
    sc.register(selector, SelectionKey.OP_READ);
    new Thread(new MyClientThread()).start();
    //检测键盘输入
    Scanner scan = new Scanner(System.in);
    while(scan.hasNext()){
    String line = scan.nextLine();
    sc.write(charset.encode(line));
    }
    }
    //每个客户端启动一个该线程,用于显示服务器发回来的消息
    private class MyClientThread implements Runnable{
    @Override
    public void run() {
    try{
    while(selector.select() > 0){
    for(SelectionKey sk : selector.selectedKeys()){
    selector.selectedKeys().remove(sk);
    if(sk.isReadable()){
    SocketChannel sc = (SocketChannel) sk.channel();
    ByteBuffer buff = ByteBuffer.allocate(1024);
    String content = "";
    while(sc.read(buff) > 0){
    buff.flip();
    content += charset.decode(buff);
    }
    System.out.println("聊天信息:" + content);
    sk.interestOps(SelectionKey.OP_READ);
    }
    }
    }
    }catch(IOException e){
    e.printStackTrace();
    }
    }
    }
    public static void main(String[] args) throws IOException{
    new NClient().init();
    }
    }

UDP

  • UDP是无连接的,只负责发送带有目标地址的数据包(DategramPacket),数据是否被正确接收,发送端并不知道。
  • 服务器端实例

    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
    public class UdpServer {
    public static final int PORT = 30001;
    private static final int DATA_LEN = 4096;
    byte[] inBuff = new byte[DATA_LEN];
    //创建DatagramPacket,传入的byte[]的长度决定了该DatagramPacket能放入多少数据
    private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
    private DatagramPacket outPacket;
    String[] data = new String[] { "aaa", "bbb", "ccc" };
    public void init(){
    try( //创建DatagramSocket,绑定端口
    DatagramSocket socket = new DatagramSocket(PORT);)
    {
    for(int i = 0; i < 1000 ; i++){
    //阻塞方法,接收传入指定端口的DatagramPacket
    socket.receive(inPacket);
    //inPacket中的数据和创造inPacket时传入的byte[]中的数据是一样的,说明inPacket的数据是存在byte[]数组中的
    //下面输出true
    System.out.println(inBuff == inPacket.getData());
    //输出inPacket中的内容
    System.out.println(new String(inBuff,0,inPacket.getLength()));
    byte[] sendData = data[i%3].getBytes();
    //创建要发送的DatagramPacket,这时候除了要发送数据的byte[]数组,还要传入目标IP和端口
    //getSocketAddress()返回的是new InetSocketAddress(getAddress(), getPort())
    outPacket = new DatagramPacket(sendData , sendData.length,inPacket.getSocketAddress());
    //发送数据包
    socket.send(outPacket);
    }
    }catch(IOException e){
    e.printStackTrace();
    }
    }
    public static void main(String[] args){
    new UdpServer().init();
    }
    }
  • 客户端实例

    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
    public class UdpClient {
    public static final int PORT = 30001;
    public static final String DEST_IP = "127.0.0.1";
    private static final int DATA_LEN = 4096;
    byte[] inBuff = new byte[DATA_LEN];
    private DatagramPacket inPacket = new DatagramPacket(inBuff , inBuff.length);
    private DatagramPacket outPacket = null;
    public void init() throws IOException{
    try(DatagramSocket socket = new DatagramSocket()){
    //使用一个空数组构造要发送的数据包,同时也要传入接受端的IP和端口
    outPacket = new DatagramPacket(new byte[0] , 0 ,InetAddress.getByName(DEST_IP),PORT);
    Scanner scan = new Scanner(System.in);
    while(scan.hasNext()){
    byte[] buff = scan.nextLine().getBytes();
    //重新设置要发送的数据包中的数据
    outPacket.setData(buff);
    socket.send(outPacket);
    //阻塞方法
    socket.receive(inPacket);
    System.out.println(new String(inBuff,0,inPacket.getLength()));
    }
    }
    }
    public static void main(String[] args) throws IOException{
    new UdpClient().init();
    }
    }