7.4 (java学习笔记)网络编程之TCP
一、tcp
1.1 tcp(transmission control protocol 传输控制协议),是一种面向连接的,安全的传输协议,但效率相比于udp而言比较低。
tcp传输时需要确保先建立连接之后,再进行传输这样就保证了传输的可靠性。
java中将tcp封装成了对应的类。
serversocket:服务端
socket:客户端
1.2tcp连接的建立与取消(三次握手与四次挥手)
连接(三次握手):
1.初始状态,服务器处于监听状态,主机的传输控制模块(tcb)像服务器发送连接请求,客户端进入同步已发送状态。
2.服务器受到客服端发送的连接请求,如果同同意连接则向客户端发送确认,服务器进入同步收到状态。
3.客户端受到确认后,继续给服务器发送确认报文,客户端进入已连接状态。
后续服务器收到客服端的确认后也进入已建立连接状态。
建立连接后,客户端和服务器就可以愉快的发送信息了,信息发送完毕后,就要断开连接。
断开(四次挥手):
1.客户端发送释放报文,同时停止发送数据主动关闭tcp连接,进入终止等待状态1。
2.服务器收到释放报文后发送确认,此时服务器进入关闭等待状态。此时客户端到服务器方向的连接就释放了。
此时tcp进入半连接状态,服务器到客户端的连接未释放,此时服务器还可以将未发送完的数据向客户端发送。
3.服务器没有数据向客户端发送之后,就会发出连接释放报文等待客户端确认,服务器进入最终确认状态。
4.客户端收到服务器发送的释放报文后,向服务器发送一个确认报文,服务器进入连接关闭状态。客户端同时进入时间等待(time-wait)状态。
此时连接还没有被释放掉。客户端会等待2msl的时间,然后进入连接关闭状态。至此连接断开完成。
每一条tcp的连接唯一的被两个通信两端的两个端点表示,也就是是四元组(源ip,源端口,目的ip,目的端口),
而不是单纯的用一个ip地址和端口区别。
也就意味着一个tcp可以建立多个连接,比如服务器ip是127.0.0.1,端口是8888;
例如客户端一:127.0.0.1:3389
客户端二:127.0.0.1:3390
客户端三:127.0.0.1:3390
三个连接对应的四元组
tcp 127.0.0.1:3889 127.0.0.1:8888 established
tcp 127.0.0.1:3890 127.0.0.1:8888 established
tcp 127.0.0.1:3891 127.0.0.1:8888 established
我们可以发现即使目的地ip和端口相同,但本地的端口不同导致整个四元组不同。
服务器可以建立多个连接,前提是四元组不同。连接中无法出现两个四元组相同的连接。
tcp可以连接多个客户端,为其每一个客户端创建一个socket,socket不同代表不同连接。
客户端服务器之间通过socket通信,服务器加上多线程为每一个socket分配一个线程就可实现并发处理。
参考:1、计算机网络(第四版) 谢希仁编著。
2、https://www.cnblogs.com/andya/p/7272462.html
3、
二、serversocket
serversocket(int port)//创建绑定到指定端口的服务器套字节。
默认绑定的ip地址是本地的ip地址。
例如我这里是在个人电脑上面运行,绑定的地址就是当前主机的ip地址。
当前ip地址可按win键+r输入cmd,然后输入ipconfig -all查看以太网适配器的ipv4地址,后面带有首选的。
也可以认为是绑定到127.0.0.1上,因为当前c/s都是在一台电脑上运行都属于本机访问,
所以本地测试使用的回环地址(127.0.0.1和本机ip(192.168.190.1)都可以。
2.主要方法
socket accept()//监听要对当前对象ip上指定端口的连接,如果发现有连接请求则连接它。
例如客户端发送一个连接请求到当前服务端的对应端口,则建立客户端与服务端的连接。
这个监听是一个阻塞式的监听,意思就是说如果没有建立连接的话当前进程就不会继续向下运行。
成功建立连接后,会返回一个socket对象,而socket对象中有获取输入输出流的方法,这时就在
客户端,服务端之间建立输入输出流管道,两者就可以通过这个管道通信。
三、socket
1.构造方法:
socket(inetaddress address, int port)
socket(string host, int port)
//创建套字节,并将其绑定到指定的(ip|域名)上的指定端口。
2.主要方法:
inputstream getinputstream()//返回当前socket对象的输入流
outputstream getoutputstream()//返回当前socket对象的输出流
四、例子
server:
import java.io.bufferedwriter; import java.io.ioexception; import java.io.outputstreamwriter; import java.net.serversocket; import java.net.socket; import java.net.unknownhostexception; public class server { public static void main(string[] args) throws unknownhostexception, ioexception { string msg = "欢迎连接到server!"; serversocket server = new serversocket(8880);//绑定到本地ip的8880端口 socket socket = server.accept();//阻塞式接收,接收成功建立连接管道 //连接管道的输出流,即对连接对象(客户端)进行输出。 bufferedwriter bos = new bufferedwriter(new outputstreamwriter(socket.getoutputstream(),"utf-8")); bos.write(msg);//服务器将指定内容发给客户端 bos.newline(); bos.flush(); } }
client:
import java.io.bufferedreader; import java.io.filedescriptor; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstreamreader; import java.io.printstream; import java.net.socket; import java.net.unknownhostexception; public class client { public static void main(string[] args) throws unknownhostexception, ioexception { socket client = new socket("192.168.190.1",8880);//向指定ip地址的指定端口进行连接 // socket client = new socket("127.0.0.1",8880);//使用127.0.0.1和使用192.168.190.1都可以完成通信 //连接成功后,获取连接管道的输入流,即对服务器写入内容进行读取 bufferedreader isr = new bufferedreader(new inputstreamreader(client.getinputstream(),"utf-8")); string re = isr.readline();//读取内容 system.out.println(re); } }
运行结果: 欢迎连接到server!
先运行server会进行阻塞式接收,没有建立连接前后面的语句都不会执行。
然后运行client建立连接后,server向连接管道中写入数据,client向连接管道中读取数据。
最后将内容显示到控制台。
五、简易聊天室
下面结合多线程,和网络编程实现一个简易聊天室。
客户端先将消息发送到服务器,服务接收消息后转发给其他客户端。
每个客户端是一个线程。
基本流程:
1.a客户端读取键盘输入数据,并将其发送到服务器。
2.服务器与a客户端建立连接后,将a客户端放入一个容器,同时将a客户端发送的消息,转发给容器中除a客户端之外的所有客户端。
服务器中为每一个socket分配一个线程,就可以实现并发转发所有聊天消息。
3.发送给其他客户端后,其他客户端会读取服务器发送的内容并显示到自己控制台。
send:读取键盘输入内容并将其发送给服务器
import java.io.bufferedreader; import java.io.datainputstream; import java.io.dataoutputstream; import java.io.ioexception; import java.io.inputstreamreader; import java.io.reader; import java.net.socket; public class send implements runnable { private boolean running = true; private datainputstream dis;//用于读取service返回的消息 private dataoutputstream dos;//用于向server发送消息 private bufferedreader br; public send(){ } public send(socket client){ try { dis = new datainputstream(client.getinputstream()); dos = new dataoutputstream(client.getoutputstream()); } catch (ioexception e) { // todo auto-generated catch block system.err.println("初始化连接失败!"); running = false; try { dis.close(); dos.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常!"); } } } //读取键盘输入信息并返回 private string reciver(){ string msg=null; br = new bufferedreader(new inputstreamreader(system.in)); try { msg = br.readline(); } catch (ioexception e) { // todo auto-generated catch block system.out.println("读取用户输入异常!"); running = false; try { br.close(); } catch (ioexception e1) { // todo auto-generated catch block e1.printstacktrace(); } } return msg; } //将键盘输入信息发送至服务器 private void send(){ string msg = reciver(); try { if(msg != null && !msg.equals("")){ dos.writeutf(msg); dos.flush(); } } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); system.err.println("用户发送信息异常!"); running = false; try { dos.close(); } catch (ioexception e1) { // todo auto-generated catch block e1.printstacktrace(); } } } public void run(){ while(running){ send(); } } }
reciver:读取服务器发送的数据
package chatroom; import java.io.datainputstream; import java.io.ioexception; import java.net.socket; public class reciver implements runnable{ private boolean running = true; private datainputstream dis; public reciver(){ } //初始化,获取连接 public reciver(socket client){ try { dis = new datainputstream(client.getinputstream()); } catch (ioexception e) { // todo auto-generated catch block system.err.println("client-->server连接失败!"); running = false; try { dis.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常!"); } } } //读取客户端发送的数据 private string reciver(){ string msg=null; try { msg = dis.readutf(); } catch (ioexception e) { // todo auto-generated catch block system.err.println("接受客户端消息异常!"); running = false; try { dis.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常!"); } } return msg; } public void run(){ while(running){ system.out.println(reciver()); } } }
client:(192.168.1.1~253) 255.255.255.0
import java.io.ioexception; import java.net.socket; import java.net.unknownhostexception; public class client { public static void main(string[] args) throws unknownhostexception, ioexception { // todo auto-generated method stub socket client = new socket("192.168.1.254",8888);//连接服务器 new thread(new send(client)).start();//读取键盘数据并发送给服务器 new thread(new reciver(client)).start();//读取服务器发送回来的消息 } }
server: 192.168.1.254 255.255.255.0
import java.io.datainputstream; import java.io.dataoutputstream; import java.io.ioexception; import java.net.serversocket; import java.net.socket; import java.util.linkedlist; import java.util.list; public class server { static list<server.connect> alluser; public static void main(string[] args) throws ioexception{ alluser = new linkedlist<server.connect>();//存储客户端的容器 serversocket serversocket = new serversocket(8888);//设置监听端口 while(true){//不断接受客户端的连接请求 socket con = serversocket.accept();//获取服务器与客户端的socket // system.out.println(con.getport()); server server = new server();//实例化一个服务器 server.connect connect = server.new connect(con);//创建一个客户端到服务器的连接(socket) alluser.add(connect);//将已经连接的客户端放入容器,也可以看做将socket放入服务器 new thread(connect).start();//每连接一个客户端(socket)就为其开辟一条线程,一个服务器对应多个客户端。 } } class connect implements runnable{// private boolean running = true;//运行标志位 datainputstream dis; dataoutputstream dos; public connect(){ } public connect(socket client){//客户端连接上服务器后的socket try {//初始化获取对客户的读写流 dis = new datainputstream(client.getinputstream()); dos = new dataoutputstream(client.getoutputstream()); } catch (ioexception e) { // todo auto-generated catch block system.err.println("server-->client连接失败!"); try { dos.close(); dis.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常"); } } } public string reciver(){//读取客户端发送的消息 string msg = null; try { msg = dis.readutf(); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); system.err.println("获取客户端信息异常!"); running = false; try { dis.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常"); } } return msg;//返回读取的消息 } public void send(string msg){//将消息发送到输出流dos对应的客户端 try { dos.writeutf(msg); dos.flush(); } catch (ioexception e) { // todo auto-generated catch block system.err.println("发送客户端信息异常!"); running = false; try { dos.close(); } catch (ioexception e1) { // todo auto-generated catch block system.err.println("关闭异常"); } } } public void sendother(){//将消息发送到其它客户端,例如a客户端发送过来的消息,就发送给除a之外的客户端 string msg = this.reciver(); system.out.println(msg); for(connect temp : alluser){//遍历存放客户端的容器 if(temp == this)//如果容器中当前对象时是a,就跳过这次循环,不是则将消息发送到对应的客户端。 continue; temp.send(msg);//哪一个客户端调用就将消息发给谁,假如这里的temp是b就将调用b中的send,此时发送的输出流是向客户端b写入的。 } } @override public void run() {//开启多线程后服务器不断接收客户端消息,然后转发 // todo auto-generated method stub while(running){//运行标志位,如果中途出现读写异常则终止。 sendother(); } } } }
如果是一台电脑上测试,则将客户端中连接服务器的地址修改为127.0.0.1或localhost端口任选(大于1024即可)。
如果是多台电脑测试,例如两台电脑(将两台电脑的网线接口用一根网线连接)。
将其中一台电脑的ip地址修改为192.169.1.245:255.255.255.0,
另外一台ip地址只需和其保持同一网段即可例如(192.168.1.1:255.255.255.0)。(最好禁用其余网卡)
在192.168.1.254上先运行服务器然后运行客户端,在另外一个电脑上运行客户端。
通过控制台输入可以实现聊天。
如果想实现同一网段多个电脑间通信需要用到交换机连接多个电脑。