socket整理复习

  1. socket参考文章
    理论讲解:https://www.jianshu.com/p/066d99da7cbd

一.socket(套接字)

  1. 定义:计算机之间进行通信的一种约定,通过这种约定,接收或者发送其他计算机的数据。

二.进程如何通信

  1. 本地进程间通信

a、消息传递(管道、消息队列、FIFO)
b、同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
c、共享内存(匿名的和具名的,eg:channel)
d、远程过程调用(RPC)

  1. 网络进程通信
  1. 我们要理解网络中进程如何通信,得解决两个问题:
      a、我们要如何标识一台主机,即怎样确定我们将要通信的进程是在那一台主机上运行。
      b、我们要如何标识唯一进程,本地通过pid标识,网络中应该怎样标识?
  2. 解决办法:
      a、TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机
      b、传输层的“协议+端口”可以唯一标识主机中的应用程序(进程),因此,我们利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互

三. Socket怎么通信

Socket通信的数据传输方式,常用的有两种:
  a、SOCK_STREAM:表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。(TCP)
  b、SOCK_DGRAM:表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。(UDP)

四.tcp的三次握手和四次挥手

https://editor.csdn.net/md/?articleId=102514909

五. TCP的粘包问题以及数据的无边界性: https://blog.csdn.net/m0_37947204/article/details/80490512

六. socket缓冲区以及阻塞模式

socket缓冲区
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取

20180528234331238.jpeg
这些I/O缓冲区特性可整理如下:

(1)I/O缓冲区在每个TCP套接字中单独存在;
(2)I/O缓冲区在创建套接字时自动生成;
(3)即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
(4)关闭套接字将丢失输入缓冲区中的数据。

输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:

unsigned optVal;
int optLen = sizeof(int);
getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
printf(“Buffer length: %d\n”, optVal);

阻塞模式
对于TCP套接字(默认情况下),当使用 write()/send() 发送数据时:

  1. 首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据。
  2. 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。
  3. 如果要写入的数据大于缓冲区的最大长度,那么将分批写入。
  4. 直到所有数据被写入缓冲区 write()/send() 才能返回。

当使用 read()/recv() 读取数据时:

  1. 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
  2. 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。
  3. 直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。
    这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

TCP套接字默认情况下是阻塞模式

socket简单通信例子

1.服务端

package bio.server;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class BioServer extends Thread {
    ServerSocket server = null;
    Socket socket = null;

    public BioServer(int port) {
        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        super.run();
        try {
            System.out.println(getdate() + "  等待客户端连接...");
            socket = server.accept();
            new sendMessThread().start();// 连接并返回socket后,再启用发送消息线程
            System.out.println(getdate() + "  客户端 (" + socket.getInetAddress().getHostAddress() + ") 连接成功...");
            InputStream in = socket.getInputStream();
            int len = 0;
            byte[] buf = new byte[1024];
            while ((len = in.read(buf)) != -1) {
                System.out.println(getdate() + "  客户端: ("
                        + socket.getInetAddress().getHostAddress() + ")说:"
                        + new String(buf, 0, len, "UTF-8"));
            }

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

    //获取时间
    public static String getdate() {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String result = format.format(date);
        return result;
    }

    //服务端发送消息线程
    class sendMessThread extends Thread {
        @Override
        public void run() {
            super.run();
            Scanner scanner = null;
            OutputStream out = null;
            try {
                if (socket != null) {
                    scanner = new Scanner(System.in);
                    out = socket.getOutputStream();
                    String in = "";
                    do {
                        in = scanner.next();
                        //设置编码
                        out.write(("" + in).getBytes("UTF-8"));
                        out.flush();// 清空缓存区的内容
                    } while (!in.equals("q"));
                    scanner.close();
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    // 函数入口
    public static void main(String[] args) {
        BioServer server = new BioServer(1234);
        server.start();
    }
}
  1. 客户端
package bio.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class BioClient extends Thread {

    //定义一个Socket对象
    Socket socket = null;

    //构造方法
    public BioClient(String host, int port) {
        try {
            //需要服务器的IP地址和端口号,才能获得正确的Socket对象
            socket = new Socket(host, port);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    @Override
    public void run() {
        //客户端一连接就可以写数据给服务器了
        new sendMessThread().start();
        super.run();
        try {
            // 读Sock里面的数据
            InputStream s = socket.getInputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = s.read(buf)) != -1) {
                //使用统一编码
                System.out.println(getdate() + "  服务器说:  "+new String(buf, 0, len,"UTF-8"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //往Socket里面写数据,需要新开一个线程
    class sendMessThread extends Thread{
        @Override
        public void run() {
            super.run();
            //写操作
            Scanner scanner=null;
            OutputStream os= null;
            try {
                scanner=new Scanner(System.in);
                os= socket.getOutputStream();
                String in="";
                do {
                    in=scanner.next();
                    os.write((""+in).getBytes());
                    os.flush();
                } while (!in.equals("bye"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            scanner.close();
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String getdate() {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String result = format.format(date);
        return result;
    }

    //函数入口
    public static void main(String[] args) {
        //需要服务器的正确的IP地址和端口号
        BioClient clientTest=new BioClient("127.0.0.1", 1234);
        clientTest.start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值