开启服务端以及基础知识铺垫
第一步,写一个服务端ServerSocketTest:
public class ServerSocketTest {
private static final ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) throws IOException {
// socket()+bind()+listen()
ServerSocket serverSocket = new ServerSocket(8090);
System.out.println("step1: new ServerSocket(80) ");
while (true) {
//accept()
Socket socket = serverSocket.accept();
Runnable task = () ->{
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
};
executorService.submit(task);
}
}
}
第二步:将java文件和class文件上传到linux系统中,本文放到了/home/binto/socketTest/
下
第三步:linux中输入,用于查看程序对系统有没有系统调用:
strace -ff -o ./ooxx java TestSocket
执行后,新开一个窗口,可以看到,多了很多ooxx.数字
的文件,每一个文件都代表着一个线程(这里的线程是java启动的线程,还不是我们代码里要生成的线程)。
第四步,查看main线程是哪一个:
grep 'step1' ./*
第五步,ooxx.13841这个文件后面再来看
第六步,输入jps
看看进程号:
第七步:cd /proc/13840
然后进入task cd task
,这里可以看到所有的线程
退回上一级文件夹,然后进入fd文件夹cd ../fd
:
可以看到,文件夹中,0-5五个数字,这五个数字就是linux的文件描述符(fd),数字4和5分别表示两个socket(ipv4和ipv6)。
客户端访问
第一步:输入netstat -natp
,查看网络监听状态:
第二步:新建一个窗口,输入nc localhost 8090
表示客户端连接socket服务器:
再看/proc/13840/fd
文件夹时,会发现多了一个socket(client的socket)
另外,socketTest/
文件夹下还多了一个文件ooxx.15155
,这是因为我们的代码中,收到一个client连接就会new 一个线程,这个文件就是那个线程的系统调用。
ooxx文件解释
现在回到socketTest/
文件夹下的ooxx.13841文件:vi ooxx.13841
第一步:搜索socket():/socket(
这一行就表示建立了一个socket,结果为5,以后想对这个socket进行操作,也就是对这个文件描述符5进行操作。
第二步:搜索bind():/bind(
可以看到,这里就是对文件描述符5进行bind()操作,同时绑定的端口号是8090。
第三步:搜索listen()
这里就不搜索了,因为上面bind()的图片可以看到,绑定后立马调用了listen()来对文件描述符5进行监听
第四步:搜索accept()
搜索文件中的client的端口号42010:/42010
可以看到,调用了系统指令accept(),这里的6指的是,监听到的client设置为文件描述符6
第五步:
我们来看ooxx.15155
这个文件:
可以看到,这个线程在recvfrom方法阻塞住了。
接着客户端访问
回到客户端的窗口,随便输入一段字符串,比方说hello word
:
此时服务端会收到信息并输出:
此时再看来看ooxx.15155
这个文件:
可以看到recvfrom的阻塞通了,收到了消息,然后处理完又进入阻塞。
总结:
对于每一个线程而言,都会进入到recvfrom阻塞。
当有client输入数据后,数据会进入到文件描述符6中,然后recvfrom会读到6中的数据,再传递给程序(用户态)。
同时,socket通信时,会走很多系统调用。
整体流程: