TCP流套接字编程
大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流
作者简介:
- CSDN java领域新星创作者blog.csdn.net/bug…
- 掘金LV3用户 juejin.cn/user/bug…
- 阿里云社区专家博主,星级博主,developer.aliyun.com/bug…
- 华为云云享专家 bbs.huaweicloud.com/bug…
TCP流套接字编程
我们知道TCP
是有连接的!
所以我们需要先对客户端建立连接!
我们来学习一下TCP
协议对应的Socket API
!
我们先来学习两个类:
通过这两个类我们就可以对TCP
协议网络编程!
这两个类也在java.net
包下!
ServerSocket
这个类实现了服务器套接字。
服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。
服务器套接字的实际工作由SocketImpl
类的实例执行。 应用程序可以更改创建套接字实现的套接字工厂,以配置自己创建适合本地防火墙的套接字!
构造方法:
ServerSocket()
创建未绑定的服务器套接字。ServerSocket(int port)
创建绑定到指定端口的服务器套接字。ServerSocket(int port, int backlog)
创建服务器套接字并将其绑定到指定的本地端口号,并指定了积压。ServerSocket(int port, int backlog, InetAddress bindAddr)
创建一个具有指定端口的服务器,侦听backlog
和本地IP
地址绑定。
我们主要用到的是第二个构造方法!我们的服务器一般都需要绑定端口号,便于客户端访问!
这个类一般用于服务器程序!
需要用到的方法
Socket
accept()
倾听要连接到此套接字并接受它!
通过这个方法,我们就可以与客户端建立连接!
返回的Socket
对象,我们后面的操作就对socket
对象进行就好了!
ServerSocket
对象就完成了他的任务和使命了!
就好比之前的电话接线员,连接后就和他没有关系了!剩下的就交个两个打电话的人了!这里的也是剩下的就交给socket
对象就好了!
用通俗的话讲就是,这里的ServerSocket
就做了一件事情,通过网络请求,与客户端建立连接,执行完后就将结果返回给请求者!
Socket
该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点。
套接字的实际工作由SocketImpl类的实例执行。 应用程序通过更改创建套接字实现的套接字工厂,可以配置自己创建适合本地防火墙的套接字。
这里的Socket
对象就相当于接电话的两个人,下面的通讯操作,主要通过这个类来实现!
构造方法
Socket()
创建一个未连接的套接字,并使用系统默认类型的SocketImpl。Socket(InetAddress address, int port)
创建流套接字并将其连接到指定IP地址的指定端口号。
我们主要学习这两个构造方法!
第一个无参构造,一般用于服务器!当ServerSocket
对象调用accept
方法后就可以用这个对象接收!因为这个socket
对象是来自客户端,所有已经有了端口号!
而第二个构造方法一般用于创建服务器socket
!
注意:
这里的address
和port
并不是像UDP
一样设置直接的端口号,而是连接到这个IP
和端口号的服务器!
需要用到的方法
void close()
关闭此套接字。InetAddress getInetAddress()
返回套接字所连接的地址。InetAddress getLocalAddress()
获取套接字所绑定的本地地址。
这里一个是连接的服务器地址,一个是自己的地址!
int getLocalPort()
返回此套接字绑定到的本地端口号。InputStream getInputStream()
返回此套接字的输入流。OutputStream getOutputStream()
返回此套接字的输出流。
我们上面的这两个方法就实现了TCP
面向字节流!
通过操作上面的两个对象就可以实现通信,输入和输出了!
TCP
面向字节流网络编程案例
- 回显服务客户端服务器
//回显服务服务器
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
//1.ServerSocket 只负责建立连接!
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//因为TCP是有连接的,我们要先建立连接(接电话)
//ServerSocket只做了一件事情,调用accept方法建立连接
//我们就得到了socketClient客户!!!
//后面就只需要针对socketClient进行操作即可!
//如果没有客户端连接,accept就会阻塞!
Socket socketClient = serverSocket.accept();
processConnection(socketClient);
}
}
public void processConnection(Socket socketClient) {
//TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
try(InputStream inputStream = socketClient.getInputStream()) {
//调用getInputStrem方法获取到请求,用输入字节流接收!
try(OutputStream outputStream = socketClient.getOutputStream()){
//调用getOutStrem方法,用于输出应答!
//通过scanner读取请求更便利!
Scanner scanner = new Scanner(inputStream);
//循环处理每一个请求,返回对应响应!
while(true){
if(!scanner.hasNext()){
System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
break;
}
//获取到请求
String request = scanner.next();
//根据请求给出应答
String response = process(request);
//把这个响应给客户端!
//通过printwiter包裹OutputSteam!
PrintWriter printWriter = new PrintWriter(outputStream);
//将请求字符串写入到输出流中!
printWriter.println(response);
printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭资源!
socketClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String process(String request) {
//回显
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
//回显服务客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
//客户端
//这里的IP和端口是服务器的,并且这里传入IP和port表示与这个服务器建立连接!
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("和服务器连接成功!");
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try(OutputStream outputStream = socket.getOutputStream()){
while(true){
//1.从控制台读取请求
System.out.print("->");
String request = scanner.next();
//2.构造请求并发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
//3.读取响应
Scanner scannerResponse = new Scanner(inputStream);
String response = scannerResponse.next();
//把结果打印到控制台!
System.out.printf("req:%s,res:%s\n",request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭资源
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
启动服务器:
启动客户端:
可以看到,这里我们也实现了回显服务!
当我们多开几个客户端试试!
可以看到客户端并没有收到应答,服务器也没和客户端建立连接!
因为,我们的ServerSocket
对象,调用了accept
后就一直在循环中,如果当前客户端不和服务器断开连接的话,就不会和其他客户端建立连接了!
我们如何才能实现多客户端呢?
我们回顾之前的对线程操作!我们可以有多个线程同时对多个客户端建立连接!!!
那我们进行升级一下变成多进程版本的服务器!
//多线程版本服务器
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpThreadEchoServer{
//1.ServerSocket 只负责建立连接!
ServerSocket serverSocket = null;
public TcpThreadEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//因为TCP是有连接的,我们要先建立连接(接电话)
//ServerSocket只做了一件事情,调用accept方法建立连接
//我们就得到了socketClient客户!!!
//后面就只需要针对socketClient进行操作即可!
//如果没有客户端连接,accept就会阻塞!
Thread thread = new Thread(()->{
Socket socketClient = null;
try {
socketClient = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
processConnection(socketClient);
});
thread.start();
}
}
public void processConnection(Socket socketClient) {
//TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
try(InputStream inputStream = socketClient.getInputStream()) {
//调用getInputStrem方法获取到请求,用输入字节流接收!
try(OutputStream outputStream = socketClient.getOutputStream()){
//调用getOutStrem方法,用于输出应答!
//通过scanner读取请求更便利!
Scanner scanner = new Scanner(inputStream);
//循环处理每一个请求,返回对应响应!
while(true){
if(!scanner.hasNext()){
System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
break;
}
//获取到请求
String request = scanner.next();
//根据请求给出应答
String response = process(request);
//把这个响应给客户端!
//通过printwiter包裹OutputSteam!
PrintWriter printWriter = new PrintWriter(outputStream);
//将请求字符串写入到输出流中!
printWriter.println(response);
printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭资源!
socketClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String process(String request) {
//回显
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer tcpThreadEchoServer = new TcpThreadEchoServer(9090);
tcpThreadEchoServer.start();
}
}
我们只更改了一点代码就实现了多个客户端!
我们来学习了线程池,我们在更改成线程池版本!
//线程池版本服务器
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpThreadPoolEchoServer {
//1.ServerSocket 只负责建立连接!
ServerSocket serverSocket = null;
public TcpThreadPoolEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
//因为TCP是有连接的,我们要先建立连接(接电话)
//ServerSocket只做了一件事情,调用accept方法建立连接
//我们就得到了socketClient客户!!!
//后面就只需要针对socketClient进行操作即可!
//如果没有客户端连接,accept就会阻塞!
//线程池版本!
Socket socketClient = serverSocket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
}
});
processConnection(socketClient);
}
}
public void processConnection(Socket socketClient) {
//TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
try(InputStream inputStream = socketClient.getInputStream()) {
//调用getInputStrem方法获取到请求,用输入字节流接收!
try(OutputStream outputStream = socketClient.getOutputStream()){
//调用getOutStrem方法,用于输出应答!
//通过scanner读取请求更便利!
Scanner scanner = new Scanner(inputStream);
//循环处理每一个请求,返回对应响应!
while(true){
if(!scanner.hasNext()){
System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
break;
}
//获取到请求
String request = scanner.next();
//根据请求给出应答
String response = process(request);
//把这个响应给客户端!
//通过printwiter包裹OutputSteam!
PrintWriter printWriter = new PrintWriter(outputStream);
//将请求字符串写入到输出流中!
printWriter.println(response);
printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//关闭资源!
socketClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String process(String request) {
//回显
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadPoolEchoServer tcpThreadPoolEchoServer = new TcpThreadPoolEchoServer(9090);
tcpThreadPoolEchoServer.start();
}
}
我们对TCP
和UDP
socket API
网络编程的学习就到此为止,还有其他的内容,我们下次学习!
- 点赞
- 收藏
- 关注作者
评论(0)