JAVASE之socket编程

本文详细介绍了TCP与UDP两种通信方式的基本原理及编程实践。包括TCP的客户端和服务端编程流程,多线程处理机制,以及UDP的数据包构建与接收过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCP通信







server:



package day07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务端应用程序
 * @author Administrator
 *
 */
public class Server {
    //运行在服务端的Socket
    private ServerSocket server;
    /**
     * 构造方法,用于初始化服务端
     * @throws IOException 
     */
    public Server() throws IOException{
        try {
            /*
             * 创建ServerSocket时需要指定服务端端口
             */
            System.out.println("初始化服务端");
            server = new ServerSocket(8088);
            System.out.println("服务端初始化完毕");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            //没有创建成功。抛出异常
            throw e;
        }
    }
    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try{
            /*
             * ServerSocket的accept方法
             * 用于监听8088端口,等待客户端的链接
             * 该方法是一个阻塞方法,直到一个客户端链接,否则该方法一直阻塞。
             * 若一个客户端链接了,会返回该客户端的Socket
             * 
             */
            System.out.println("等待客户端连接......");
            Socket socket=server.accept();
            System.out.println("客户端连接了");


            //接收来自客户端的消息
            //InputStream输入流

           InputStream is = socket.getInputStream();
            //变成字符流!!!
           InputStreamReader isr = new InputStreamReader(is);
           //将字符流转换为缓冲流输入流,这样就可以按行为单位读取字符串了
           BufferedReader br = new BufferedReader(isr);
           String message;

           while((message=br.readLine())!=null){
           System.out.println("客户端:"+"IP:"+socket.getInetAddress()+":"+socket.getPort()+"::"+message);
           }
        //若readLine返回null就表示无法再读取到信息
          //windows:当客户端与服务端断开连接后readLine()方法会抛出异常

        }catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void main(String[] args){

        Server server = null;
        try {
            server = new Server();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("服务端初始化失败!");
        }
        server.start();
    }

}
client:




package day07;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {
//Socket,用于连接服务端的ServerSocket
    private Socket socket;
    //构造方法,初始化client
    public Client() {
        try {
            /*
             * 创建Socket对象时,就会尝试根据给定的地址与端口连接服务器。所以,
             * 若该对象创建成功,说明与服务端连接正常
             */
            socket = new Socket("127.0.0.1",8088);
            System.out.println("成功连接服务端");
        } catch (UnknownHostException e) {
            //端口号异常
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("客户端创建失败");
            e.printStackTrace();
        }

    }
    //客户端启动方法
    public void start(){
        try{
            /*
             * 可以通过Socket的getOutputStream()
             * 方法获取一条输出流,用户与信息发送至服务端
             * 是一个字节流,
             */
        OutputStream out = socket.getOutputStream();
            //转换成字符流,写更容易些。
        OutputStreamWriter osw = new OutputStreamWriter(out);
        //在包装上缓冲流  以行为单位写字符串

        PrintWriter pw  = new PrintWriter(osw);
        while(true){
        Scanner scanner = new Scanner(System.in); 
        pw.println(scanner.next());
        //不写的话就不会显示,因为是缓冲流。并没有输出。自己flush一下。
        pw.flush();
        }
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {

        Client client = new Client();
        client.start();



    }

}

小结:

创建一个类,然后定义个socket,作为server和client,开始只是定义了是Socket类型,并没有初始化。实现构造函数,server的构造函数写的是端口,server = new ServerSocket();ip就是运行在哪个上面就是哪个了。client是传入的服务器的ip和端口 socket = new Socket(ip,端口)

然后都要创建个start函数用作sockt连接后的功能。
server要等待连接; accept 调用accpet是在start中的。

Socket socket=server.accept();

创建的socket是 连接客户端的 socket

然后在主函数中创建个 server
server = new Server();调用 start函数
然后是在start中写如何传输字符。socket有个getOutputStream方法,但是返回的是字节流,那么就要转成字符流 就是 OutputStreamWriter ,然后在加个缓冲流,一行一行的读写操作 PrintWriter
server: getInputStream InputStreamReader BufferedReader

clent: getOutputStream outputStreamWriter
PrintWriter

读取操作,若readLine返回的是null那么就是没有信息在读了。
要注意 flush()

多线程服务端

package day07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * java多线程socket
 * @author Administrator
 *
 */
public class ThreadServer2 {
    private ServerSocket server;

        public ThreadServer2(){
            try {
                server = new ServerSocket(8088);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

        public void start(){
            while(true){
                System.out.println("服务器等待连接.....");
            try {
                Socket socket = server.accept();
                //调用线程,传入socket
                hthread ht1 = new hthread(socket); 
                ht1.start();

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }//catch



            }//while
        }//start


    public static void main(String[] args) {
        //直接创建启动服务器
        new ThreadServer2().start();

    }
    //线程类,在start中调用线程,并传入socket
    class hthread extends Thread {
        private Socket socket;
        public hthread (Socket socket){
            this.socket = socket;
            System.out.println("客户端连接成功!");

        }

        public void run(){
            try {
                //接收数据
                InputStream is = socket.getInputStream();

                InputStreamReader isr = new InputStreamReader(is);

                BufferedReader br = new BufferedReader(isr);
                String message;
                while((message = br.readLine())!=null){

                    System.out.println("客户端:IP:"+socket.getInetAddress()+"  PORT"+socket.getPort()+": "+message);
                }//while
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{try {
                socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }}


        }


    }

}

用Runnable,然后用线程池:

package day07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import day07.ThreadServer2.hthread;

public class ThreadciServer {
    private ServerSocket server;
    private ExecutorService threadPool;

    public ThreadciServer(){
        try {
            server = new ServerSocket(8088);
            //创建线程池,最多运行50个
    threadPool =Executors.newFixedThreadPool(2);


        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void start(){
        while(true){
            System.out.println("服务器等待连接.....");
        try {
            Socket socket = server.accept();
            //调用线程,传入socket
            Runnable ht1 = new hthread(socket); 

            threadPool.execute(ht1);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }//catch



        }//while
    }//start


public static void main(String[] args) {
    //直接创建启动服务器
    new ThreadServer2().start();

}
//线程类,在start中调用线程,并传入socket
class hthread implements Runnable {
    private Socket socket;
    public hthread (Socket socket){
        this.socket = socket;
        System.out.println("客户端连接成功!");

    }

    public void run(){
        try {
            //接收数据
            InputStream is = socket.getInputStream();

            InputStreamReader isr = new InputStreamReader(is);

            BufferedReader br = new BufferedReader(isr);
            String message;
            while((message = br.readLine())!=null){

                System.out.println("客户端:IP:"+socket.getInetAddress()+"  PORT"+socket.getPort()+": "+message);
            }//while
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{try {
            socket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }}


        System.out.println("一个客户端下线了....");

    }


}

}

又小结:

返回的,server的直接就定义输出流,然后在run中原来是把信息输出在显示屏,直接就pw.println(); 发送过去。
然后在client就复杂些。不是只客户端发,然后服务器收,在返回给客户端,要无论客户端是否发,有信息就发给客户端,那么就要定义个输入流。但是单独给他创建个线程,收和发不是同步的。线程就是和服务器的接收一样的。在start方法中调用创建线程。

server:


package day07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import day07.ThreadciServer.hthread;

public class Server_fanui {
    private ServerSocket server;
    private ExecutorService threadPool;

    public Server_fanui(){
        try {
            server = new ServerSocket(8088);
            //创建线程池,最多运行50个
    threadPool =Executors.newFixedThreadPool(50);


        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void start(){
        while(true){
            System.out.println("服务器等待连接.....");
        try {
            Socket socket = server.accept();
            //调用线程,传入socket
            Runnable ht1 = new hthread(socket); 

            threadPool.execute(ht1);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }//catch



        }//while
    }//start


public static void main(String[] args) {
    //直接创建启动服务器
    new ThreadServer2().start();

}
//线程类,在start中调用线程,并传入socket
class hthread implements Runnable {
    private Socket socket;
    public hthread (Socket socket){
        this.socket = socket;
        System.out.println("客户端连接成功!");

    }

    public void run(){
        try {

            OutputStream os = socket.getOutputStream();

            OutputStreamWriter osr = new OutputStreamWriter(os);

            PrintWriter pw = new PrintWriter(osr,true);

            //接收数据
            InputStream is = socket.getInputStream();

            InputStreamReader isr = new InputStreamReader(is);

            BufferedReader br = new BufferedReader(isr);
            String message;
            while((message = br.readLine())!=null){

            //  System.out.println("客户端:IP:"+socket.getInetAddress()+"  PORT"+socket.getPort()+": "+message);
                pw.println(message);


            }//while
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{try {
            socket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }}


        System.out.println("一个客户端下线了....");

    }


}

}
client:

package day07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

import day07.Client.Getserver;

public class Client_fanhui {
    //Socket,用于连接服务端的ServerSocket
        private Socket socket;
        //构造方法,初始化client
        public Client_fanhui() {
            try {
                /*
                 * 创建Socket对象时,就会尝试根据给定的地址与端口连接服务器。所以,
                 * 若该对象创建成功,说明与服务端连接正常
                 */

                socket = new Socket("127.0.0.1",8088);
                System.out.println("成功连接服务端");
            } catch (UnknownHostException e) {
                //端口号异常
                e.printStackTrace();
            } catch (IOException e) {
                System.out.println("客户端创建失败");
                e.printStackTrace();
            }

        }
        //客户端启动方法
        public void start(){
            try{
                Runnable rserver = new Getserver();
                Thread tserver = new Thread(rserver);
                tserver.start();

                /*
                 * 可以通过Socket的getOutputStream()
                 * 方法获取一条输出流,用户与信息发送至服务端
                 * 是一个字节流,
                 */

            OutputStream out = socket.getOutputStream();
                //转换成字符流,写更容易些。
            OutputStreamWriter osw = new OutputStreamWriter(out);
            //在包装上缓冲流  以行为单位写字符串

            PrintWriter pw  = new PrintWriter(osw,true);
            while(true){
            Scanner scanner = new Scanner(System.in); 
            pw.println(scanner.next());

            //不写的话就不会显示,因为是缓冲流。并没有输出。自己flush一下。
            }
            }catch(Exception e){
                e.printStackTrace();
            }

        }

        public static void main(String[] args) {

            Client client = new Client();
            client.start();


        }
        //创建个线程,一个是原来的向服务器发数据,这个是向服务器接收数据。
        class Getserver implements Runnable{


            @Override
            public void run() {
                try{
                    System.out.println("6666");

                InputStream is = socket.getInputStream();

                InputStreamReader isr = new InputStreamReader(is);

                BufferedReader br = new BufferedReader(isr);
                String message=null;
                while((message=br.readLine())!=null){
                    System.out.println("服务器说:"+message);
                }

                }catch(Exception e){
                    e.printStackTrace();
                }


            }//run


        }//class_getserver


    }

若创建个多人同时在线聊天的方法

把别的client即在服务器是不同的线程的如何发给别的创建一个公共的list,来保存所有客户端输出流。list中放的是流。所以定义的时候 List,在构造函数中初始化。

将该客户端的输出存在在共享集合,以便使得该客户端也能接受服务器转发的消息

append(pw)
集合中存的是流!!!不是流里面的字符串!!!!!一个client对应一个list中的流。。。。
如果client断开了就要把他在list中对应的流也删除。remove(pw)

统计当前在线人数。。。list的size就是

将一个客户端发来得消息,变量list 中所有流,都发过去。实现群聊

但是会有多线程同时操作 共享集合list 。。。 线程安全问题

遍历这个集合,是迭代器,不是线程安全的,遍历与add和remove不是同步的。
要保证遍历的时候也是同步的,三个都是同步的。

要是互斥的。那就同步锁 也叫互斥锁。

我们调用静态方法的时候,不需要先生成一个实例,可以通过类名直接调用。

静态方法和静态属性为所有实例共用

因为多个线程操作一个list会造成线程不安全的问题(增加和删除,遍历没事)
用synchronized
若两个线程看到的是一个synchronized 是同步锁,不能同时访问

这里写图片描述

但是这种就是互斥,不同的地方锁住同一个对象,就是互斥锁了

可以把输出流放入map中,给每个输出流定义个名称,
然后可以根据@昵称,然后去map查找就去单独输出,否则就是全部输出

Udp通信

不可靠的传输协议
TCP连接上用的流。连续输出互相写
但是UDP不是保持长连接,发完就不管了

这里写图片描述

不需要等待对方,消耗少
主要学习的就是打包和拆包 ,打包发送过去,接收到后拆包
DatagramPacket
构建接收包

这里写图片描述

构造发送包

这里写图片描述

服务端接收
DatagramSocket

这里写图片描述

实例化的时候给一个端口号,还是服务端的端口要固定

receive接收数据

客户端发送
两端都是DatagramSocket

这里写图片描述

server:

package day07;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * UDP服务端
 * @author Administrator
 *
 */
public class UdpServer {


    public  void start(){
        try{
            /*
             * 接收包的步骤
             * 创建socket
             * 创建一个合适大小的包
             * 通过socket接收数据到包中
             * 拆包取数据
             */
            //服务端创建socket的时候要传参数端口

            DatagramSocket server
            = new DatagramSocket(8088);
            //创建直接数组存储数据
            byte  data [] = new byte[1024];
            //创建包,接收数据数据,要接收的长度
            DatagramPacket recvpacket
            = new DatagramPacket(
                    data,
                    data.length
                    );
            //接收包裹,用包来装传过来的包
            //注意,该方法是个阻塞方法,当客户端发过来包才能继续
            server.receive(recvpacket);
        //拆包,拿数据,可以这样获取,其实那个data就已经是了
        //为了以后其他操作,来拆包
        byte [] d = recvpacket.getData();
        //获取数据的实际长度
        int dlen = recvpacket.getLength();
        /*还有........
         * String(
         * byte [] b,
         * int offset,
         * int len,
         * String charsetName
         * )
         * 将给定的字节数组中,从offset处开始到len个字节,
         * 再根据给定的字符集
         * 转换为字符串
         * 
         */

            String info = new String(d);

            System.out.println("Udp客户端说:"+info);

        }catch(Exception e){
            e.printStackTrace();
        }



    }



    public static void main(String[] args) {
        UdpServer server = new UdpServer();
        server.start();
    }




}




client:


package day07;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP客户端
 * @author Administrator
 *
 */
public class UdpClient {
    //  udp客户端的启动方法
    public void start(){
        try{
            /*
             * 向服务端发送数据的步骤
             * 创建好Socket(一次就行)
             * 准备数据
             * 创建数据包
             * 将数据包存入包中(2,3是一步完成的)
             * 将数据包通过socket发送到服务端
             */
            DatagramSocket client = new DatagramSocket();

            String str = "你好!服务端";
            //创建了数据,还是要把数据转换成字节才能发送
            byte[] data = str.getBytes();

            //打包:准备包裹,填写地址,装入数据
            //地址
            InetAddress address
            = InetAddress.getByName("localhost");
            //端口
            int port = 8088;
            //创建发送包
            //数据,数据长度,ip,端口
            DatagramPacket sendPack
            = new DatagramPacket(
                data,
                data.length,
                address,
                port
                    );
            //将数据包发送出去
            client.send(sendPack);



        }catch(Exception e){
            e.printStackTrace();
        }



    }
    public static void main(String[] args) {

        UdpClient client = new UdpClient();
        client.start();


    }

}

这里写图片描述

服务端和客户端回复的

server:


package day07;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP服务端
 * @author Administrator
 *
 */
public class UdpServer {


    public  void start(){
        try{
            /*
             * 接收包的步骤
             * 创建socket
             * 创建一个合适大小的包
             * 通过socket接收数据到包中
             * 拆包取数据
             */
            //服务端创建socket的时候要传参数端口

            DatagramSocket server
            = new DatagramSocket(8088);
            //创建直接数组存储数据
            byte  data [] = new byte[1024];
            //创建包,接收数据数据,要接收的长度
            DatagramPacket recvpacket
            = new DatagramPacket(
                    data,
                    data.length
                    );
            //接收包裹,用包来装传过来的包
            //注意,该方法是个阻塞方法,当客户端发过来包才能继续
            server.receive(recvpacket);
        //拆包,拿数据,可以这样获取,其实那个data就已经是了
        //为了以后其他操作,来拆包
        byte [] d = recvpacket.getData();
        //获取数据的实际长度
        int dlen = recvpacket.getLength();
        /*还有........
         * String(
         * byte [] b,
         * int offset,
         * int len,
         * String charsetName
         * )
         * 将给定的字节数组中,从offset处开始到len个字节,
         * 再根据给定的字符集
         * 转换为字符串
         * 
         */

            String info = new String(d);

            System.out.println("Udp客户端说:"+info);


        //回复客户端

            String str = "你好!客户端";
            //创建了数据,还是要把数据转换成字节才能发送
            byte[] data2 = str.getBytes();

            //打包:准备包裹,填写地址,装入数据
            //地址
            InetAddress address
            = recvpacket.getAddress();
            //端口
            int port = recvpacket.getPort();
            //创建发送包
            //数据,数据长度,ip,端口
            DatagramPacket sendPack
            = new DatagramPacket(
                data2,
                data2.length,
                address,
                port
                    );
            //将数据包发送出去
            server.send(sendPack);






        }catch(Exception e){
            e.printStackTrace();
        }



    }



    public static void main(String[] args) {
        UdpServer server = new UdpServer();
        server.start();
    }




}
client:

package day07;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * UDP客户端
 * @author Administrator
 *
 */
public class UdpClient {
    //  udp客户端的启动方法
    public void start(){
        try{
            /*
             * 向服务端发送数据的步骤
             * 创建好Socket(一次就行)
             * 准备数据
             * 创建数据包
             * 将数据包存入包中(2,3是一步完成的)
             * 将数据包通过socket发送到服务端
             */
            DatagramSocket client = new DatagramSocket();

            String str = "你好!服务端";
            //创建了数据,还是要把数据转换成字节才能发送
            byte[] data = str.getBytes();

            //打包:准备包裹,填写地址,装入数据
            //地址
            InetAddress address
            = InetAddress.getByName("localhost");
            //端口
            int port = 8088;
            //创建发送包
            //数据,数据长度,ip,端口
            DatagramPacket sendPack
            = new DatagramPacket(
                data,
                data.length,
                address,
                port
                    );
            //将数据包发送出去
            client.send(sendPack);


            //接收服务端的数据


            byte  data2 [] = new byte[1024];
            //创建包,接收数据数据,要接收的长度
            DatagramPacket recvpacket
            = new DatagramPacket(
                    data2,
                    data2.length
                    );
            //接收包裹,用包来装传过来的包
            //注意,该方法是个阻塞方法,当客户端发过来包才能继续
            client.receive(recvpacket);
        //拆包,拿数据,可以这样获取,其实那个data就已经是了
        //为了以后其他操作,来拆包
        byte [] d = recvpacket.getData();
        //获取数据的实际长度
        int dlen = recvpacket.getLength();
        /*还有........
         * String(
         * byte [] b,
         * int offset,
         * int len,
         * String charsetName
         * )
         * 将给定的字节数组中,从offset处开始到len个字节,
         * 再根据给定的字符集
         * 转换为字符串
         * 
         */

            String info = new String(d);

            System.out.println("Udp服务端说:"+info);





        }catch(Exception e){
            e.printStackTrace();
        }



    }
    public static void main(String[] args) {

        UdpClient client = new UdpClient();
        client.start();


    }

}

我的eclipse出现了问题。。。总是运行之前的代码。上网各种查解决方法。。。全是了。。。还是不行。灵异了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值