网络协议七层结构:
什么是Socket?
socket(套接字)是两个程序之间通过双向信道进行数据交换的端,可以理解为接口。使用socket编程也称为网络编程,socket只是接口并不是网络通信协议。
HTTP协议和Socket的区别
http协议是应用层,其模式是请求-应答,客户端发送请求,服务器端进行响应。传输的数据是原始格式的数据,eg :json、xml、text等数据格式。
socket不是协议是接口,socket提供TCP/UDP socket 的实例,供java 或者其他语言操作数据的传输,socket是对传输层(TCP/UPD协议)的封装。
Socket通信分为两种
TCP Socket :使用流传输,提供inputStream 和 outputStream 方法对数据进行流操作。要理解TCP套接字首先要对TCP协议有所理解。
1)TCP协议是传输层的协议,他的下一层是IP协议(网络层),IP协议在网络数据传输是通过ip寻址,将源地址和目的地址进行连接。TCP协议是在IP协议上多加一层端口寻址,光只通过IP寻址只能定位到主机,tcp通过端口找到对应的应用程序。
2)TCP 建立连接需要三次握手,将源应用程序和目的应用程序之间搭建一个连接,所以源应用和目的应用程序之间必须是one by one。IP 协议只管数据的传输,不保证数据是否丢失,重复传,顺序是否正确,TCP会对这些问题做一些补偿机制,丢失数据重传,用队列保证数据的顺序。
3) TCP 缺点:因为每个客户端和服务器端传输数据都要建立连接,三次握手是不传输数据并且有耗时,当有大量短连接的时候并且对数据的正确性要求不高的时候,将会占用带宽。
UDP Socket:使用数据报文进行传输,创建UDP socket 发送和接收数据报文。
1)UDP协议同TCP协议一样都是应用层协议,也是通过端口寻址,找到对应的应用程序。
2)UDP传输数据报文不需要和目的应用程序建立连接,他在数据报文中指定目的主机和目的端口号,发送出的数据自动寻址到对应的主机和端口号。因为不用和目的主机建立连接,所以一个源应用程序可以以广播的形式将数据报文传输给多主机。因为不用建立连接,耗时和带宽占用量都比TCP协议更优秀
3)UDP缺点:数据有可能丢失,丢失的数据不会重传
java socket 实例
TCP Socket client
package socket.transmission.tcp; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket; //TCP 套接字 客户端负责发送请求
public class TcpClient {
private static final int BUF_SIZE=32;
/**
* TCP客户端发送一个请求要有三个步骤:
* 1.创建一个socket的实例,创建一个指向server主机ip和端口号的TCP连接
* 2.通过套接字的输入和输出流进行通信
* 3.使用socket close关闭
*/
public static void main(String[] args){
String ip="192.168.197.1";
int port=8080;
try {
// 创建一个socket实例
Socket socket=new Socket(ip,port); // 1 设置TCP SOCKET,初始化目的主机ip和端口号,建立和目的主机的连接,若目的主机没有开启服务,则会弹出server refused
System.out.println("创建一个socket连接");
InputStream inputStream=socket.getInputStream(); // 2 获取回馈服务器的输入流
OutputStream outputStream=socket.getOutputStream(); // 3 将要传输的数据数据写入到输出流中,传输给目的主机
//向socket中写入数据
outputStream.write("this is a word".getBytes()); // 4 传输数据到目的主机
int totalByrecive=0; //到目前为止接收到的数据
byte[] readBuff=new byte[BUF_SIZE];
int lastReadByte; //最后接收的字节
System.out.println("从服务器中接收的数据:");
int receiveMsgSize;
while ((receiveMsgSize=inputStream.read(readBuff))!=-1){ // 5.从回馈服务器中获取数据,
System.out.println(new String(readBuff));
}
socket.close(); //关闭
} catch (IOException e) {
e.printStackTrace();
}
} }
tcp sokect server
package socket.transmission.tcp; //TCP 服务器端进行接收请求 import sun.java2d.pipe.OutlineTextRenderer; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress; /**
* TCP服务器对客户端发送的请求会进行以下处理
* 1.创建serverSocket实例并且指定本机端口,功能:监听指定端口发送过来的连接
* 2.重复执行:
* 1).调用的serverSocket 的accept() 监听客户端发送过来的请求,并创建socket
* 2).使用socket的inputStream 和 outputStream 进行通讯
* 3).通信完使用socket.close() 方法将连接关闭
*/
public class TcpServer { private static final int BUF_SIZE=32; public static void main(String[] args){
int port=8080;
Socket socket = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
ServerSocket serverSocket=new ServerSocket(port);//创建一个socket实例用于监听客户端发送的连接,指定本服务器的端口号
System.out.println("创建serverSocket 实例");
int reviceMsgSize; // 接收msg的大小
byte[] receiveBuf=new byte[BUF_SIZE]; //创建一个信息接收的缓冲区
System.out.println("开始处理接收的数据");
while (true) {
socket = serverSocket.accept(); //接收客户端的连接,每接收一个数据都会创建一个连接,当没有数据的接收的时候会阻塞
SocketAddress socketAddress = socket.getRemoteSocketAddress(); //
System.out.println("访问的地址:" + socketAddress);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
while ((reviceMsgSize = inputStream.read(receiveBuf)) != -1) {
System.out.println(new String(receiveBuf));
outputStream.write("aaaaa".getBytes(), 0, 4);
}
outputStream.flush();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(socket!=null){
socket.close();
}
if(inputStream!=null){
inputStream.close();
}
if(outputStream!=null){
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} }
UDP Socket client
package socket.transmission.udp; import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.*; //UDP 套接字传输的是数据报文 /**
* UDP 客户端发送数据报文的步骤:
* 1.创建UPD 套接字 DataGramSocket ,使用UDP协议通信是不需要和服务器端创建的连接的,UPD协议只是在IP协议上
* 多加了一层端口寻址,设置超时
* 2.创建发送的数据报文实例DataGramPacket,若是单播的则要指明目的端的ip地址和port,IP地址通过创建InetAddress 的实例
* 3.创建接收数据报文实例DataGramPacket ,指定接收数据的缓冲区
* 4.调用socket的send方法将数据报文发送出去
* 5.循环接收数据报文,当数据报文丢失的时候,发起重试。否则设置响应标志位true,将数据打印
*/
public class UdpClient {
/**
* 当客户端发送给server端信息,收到回馈信息的时候,通过read读取数据,当没有数据返回(数据丢失)
* read 方法会发生阻塞,若没有设置超时重发,则程序会一直阻塞
*/
private static final int TIME_OUT=2000; //设置超时重发时间 private static final int MAX_RENTRY=3; // 设置最大重试次数 public static void main(String[] args) throws IOException {
try {
int serverPort=8080; // 指定
byte[] sendMsg="this is a test".getBytes();
DatagramSocket socket=new DatagramSocket(); //创建一个数据报文
socket.setSoTimeout(TIME_OUT); //设置read阻塞超时时间
byte[] ipByte={10,1,1,100};
/**
* "10.1.1.100".getbytes()的方式不能正确的创建server端,调用InetAddress.getByAddress() 方法将会做两个长度判断,IPV4 的入参长度要==4
* IPV6 的长度要== 16 而通过10.1.1.100 getbytes的方式获取的长度是10 抛出违法的长度
*/
InetAddress inetAddress= InetAddress.getByAddress(ipByte); //创建server主机的ip地址
DatagramPacket sendPacket=new DatagramPacket(sendMsg,sendMsg.length,inetAddress,8080); //发送的数据报文
DatagramPacket receivePacket=new DatagramPacket(new byte[sendMsg.length],sendMsg.length); //接收的数据报文
int tryTimes=0; //数据报文可能丢失,设置重试计数器
Boolean receiveResponse=false;
socket.send(sendPacket); //将数据报文发送出去
do{
try {
socket.receive(receivePacket); //尝试去循环接收数据报文
if (!receivePacket.getAddress().equals(inetAddress)) { //检查回馈过来的数据报文
throw new IOException("未知的Server端数据报文");
}
receiveResponse=true;
}catch (InterruptedIOException e){ //数据报文中断异常
tryTimes++;
System.out.println("超时还有"+(MAX_RENTRY-tryTimes)+"次重试机会");
}
}while(!receiveResponse&&(tryTimes<MAX_RENTRY));
if(receiveResponse){
System.out.println("从服务器端获取的数据:"+new String(receivePacket.getData()));
}else{
System.out.println("没有获取到数据");
}
socket.close(); //关闭套接字
} catch (SocketException e) {
e.printStackTrace();
}
}
}
UPD Soceket server
package socket.transmission.udp; import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.*; //UDP 套接字接收客户端的数据报文
public class UdpServer {
private static final int ECHO_MAX=255; //设置缓冲区的长度 public static void main(String[] args) {
try {
DatagramSocket datagramSocket=new DatagramSocket(8080);
DatagramPacket reveiveMsg=new DatagramPacket(new byte[ECHO_MAX],ECHO_MAX);
while(true){
datagramSocket.receive(reveiveMsg);
System.out.println("从客户端接收的来数据:"+new String(reveiveMsg.getData()));
//在服务器端将发送的信息修改
byte[] newData="啦啦啦啦".getBytes();
// reveiveMsg=new DatagramPacket(newData,newData.length);
//将转化后的数据发送
datagramSocket.send(reveiveMsg);
/**
* 重置接收包的长度,因为接收数据的时候已经接收包的长度设置为接收信息的长度,当下次再接收数据的时候,
* 新数据的长度大于上一次数据的长度时,多出的数据将被截断,所以要重置接收包缓冲区的长度
*/
reveiveMsg.setLength(ECHO_MAX);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}catch (SocketException se){
se.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
}
上面的代码还有一些未补足的:要在finally 中将所有的流关闭。