socket套接字,果断会联想到tcp/ip和udp协议,作为一个安卓程序员我也是泪崩了,对网络的东西还真的有待加强,对协议什么的讲清楚还是很有难度啊。
TCP/IP、Http、Socket的区别
其实socket是tcp/ip协议封装的一个api与协议不是同一概念。 socket是对TCP/IP协议的封装和应用(程序员层面上),TPC/IP协议是传输层协议,主要解决数据如何在网络中传输。妈蛋,这要讲网络分几层还不得不让我想起计算机网络通讯这门课,基本上不搞网络的谁还记得啊。物理层、数据链路层、网络层、传输层、会话层、表示层和应用层(现在都要百度),哈哈,但是不影响我们写代码。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,如果用UDP来的话感觉还是简单点。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。
而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
哈哈,感觉和tcp/ip的三次握手是不是特别的相似啊,我们来看下三次握手 :
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
socket中发送的TCP三次握手
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
TCP/IP和UDP的区别:
1、TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;
而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
2、也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。
知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,
因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,
即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。
Socket分类:(TCP/IP和UDP)
Socket类型为流套接字(streamsocket)和数据报套接字(datagramsocket)。流套接字将TCP作为其端对端协议,提供了一个可信赖的字节流服务。数据报套接字使用UDP协议,提供数据打包发送服务。下面我们用代码来实现:
package com.zy.scoketdemo.tcp;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
private static List<Socket> sockets;
private static Socket socket = null;
private static ServerSocket serverSocket=null;
public static void main(String[] args) throws IOException {
// 1.创建服务socket
serverSocket = new ServerSocket(8888);
System.out.println("****服务器已开启,等待客户端连接******");
InitSocket(serverSocket);
}
private static void InitSocket(ServerSocket serverSocket)
throws IOException {
sockets=new ArrayList<Socket>();
while (true) {
// 2.等待客户端的连接
socket = serverSocket.accept();
sockets.add(socket);
System.out.println("第"+sockets.size()+"个客户端已经连接");
new ScoketThreadForTcp(socket).start();
}
}
}
package com.zy.scoketdemo.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class ScoketThreadForTcp extends Thread {
private Socket socket;
private InputStream is = null;
private InputStreamReader isr = null;
private BufferedReader br = null;
private OutputStream os = null;
private PrintWriter pw = null;
public ScoketThreadForTcp(Socket socket) {
this.socket = socket;
//设置优先级
this.setPriority(4);//默认的是5,范围(1-10)
}
public void run() {
try {
// 连接客户端
System.out.println("客户端已连接");
is = socket.getInputStream();// 字节输入流
isr = new InputStreamReader(is);// 字节输入流转字符输入流
br = new BufferedReader(isr);// 字符输入流家加缓冲
String content = null;
while ((content=br.readLine()) != null) {
System.out.println("客户端:" + content);
}
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务端:我收到了你的请求");
pw.flush();
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
br=null;
}
if (isr != null) {
isr.close();
isr=null;
}
if (is != null) {
is.close();
is = null;
}
if(pw!=null){
pw.close();
pw=null;
}
if(os!=null){
os.close();
os=null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.zy.scoketdemo.tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
private static InputStream is = null;
private static InputStreamReader isr = null;
private static BufferedReader br = null;
private static OutputStream os = null;
private static PrintWriter pw = null;
public static void main(String[] args) throws UnknownHostException,
IOException {
Socket socket=new Socket(InetAddress.getLocalHost(),8888); // 如果一个主机同时属于两个以上的网络, 它就可能拥有两个以上的IP 地址
pw = new PrintWriter(os);
pw.write("你好,我是客户端1");
pw.flush();//发送数据到服务器
socket.shutdownOutput();//关闭socket的写入流
is = socket.getInputStream();// 字节输入流
isr = new InputStreamReader(is);// 字节输入流转字符输入流
br = new BufferedReader(isr);// 字符输入流家加缓冲
//设置超时间为10秒
socket.setSoTimeout(10*1000);
String content = null;
while ((content=br.readLine()) != null) {//一下读一行
System.out.println("服务端:" + content);
}
//char[]c=new char[64];
//int len=0;
//StringBuffer sb=new StringBuffer();
//while((len=br.read(c))!=-1){
// sb.append(new String(c, 0, c.length));
//}
// System.out.println("服务端: + sb);
socket.shutdownInput();
try {
if (br != null) {
br.close();
br = null;
}
if (isr != null) {
isr.close();
isr = null;
}
if (is != null) {
is.close();
is = null;
}
if (pw != null) {
pw.close();
pw = null;
}
if (os != null) {
os.close();
os = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
以下是udp实现的socket传输,也就是数据报文传输,感觉还是简单点。
package com.zy.scoketdemo.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Server {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket=new DatagramSocket(8888);//端口号必须与客户端的保持一致
byte[]b=new byte[1024];
System.out.println("****服务器已开启,等待客户端连接******");
DatagramPacket packet=new DatagramPacket(b, b.length);//为数据报文开空间
datagramSocket.receive(packet);//接收客户端请求的数据报文
String info=new String(b, 0, packet.getLength());
System.out.println("clien:"+info);
byte[]reply="很高兴,我收到了".getBytes();//要发送的字符串转为字节流
InetAddress address=packet.getAddress();//得到主机地址
int port=packet.getPort();//由数据报文得到端口
DatagramPacket rPacket=new DatagramPacket(reply, reply.length, address,port);
datagramSocket.send(rPacket);//发送数据报文
datagramSocket.close();//关闭socket
}
}
package com.zy.scoketdemo.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Client {
public static void main(String[] args) throws IOException {
InetAddress address=InetAddress.getByName("localhost");
int port=8888;
byte[]b="客户端来了".getBytes();
DatagramPacket datagramPacket=new DatagramPacket(b,b.length,address, port);
DatagramSocket socket=new DatagramSocket();
socket.send(datagramPacket);
byte[]rb=new byte[1024];
DatagramPacket packet=new DatagramPacket(rb, rb.length);
socket.receive(packet);
String content=new String(rb, 0, packet.getLength());
System.out.println("server:"+content);
}
}
总之socket间的通讯,必须先打开服务端,并且服务端只能打开一次,然后才能打开客户端,tcp/ip的步骤简单的就是ServerSocket通过端口建立socket服务监听对象,然后客户端的socket通过端口和ip进行通讯,当客户端socket.conect()时。服务端ServerSocket.accept()与客户端进行连接,这里会有阻塞我们把它放在多线程里面来处理,用死循环来进行无限连接,然后我们就可以进行自己定义的协议进行通讯,通过socket来进行收发数据,收发完了必须释放资源断开连接。
基于HTTP协议的客户/服务器模式的信息交换过程,它分四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。
所以socket和http协议的信息交换是不是很像,不多说,如果你要在安卓里面使用,请记得加上权限
<uses-permission android:name="android.permission.INTERNET"/>