TCP/IP :传输层
建立TCP 需要经过三次握手:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
断开连接需要经过四次挥手
1:客户端进程发出连接释放报文,并且停止发送数据。此时,客户端进入FIN-WAIT-1(终止等待1)状态
2:服务器收到连接释放报文,发出确认报文,服务端就进入了CLOSE-WAIT(关闭等待)状态。(此时服务器若发送数据,客户端依然要接受).客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文
3:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
4:客户端收到服务器的连接释放报文后,必须发出确认,服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接(服务器结束TCP连接的时间要比客户端早一些。)
HTTP 超文本协议 ,他是建立TCP协议之上的一种应用.
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。(短连接)
Socket 套接字是通讯的基石,是支持TCP/IP协议的网络通信的基本操作单元.
进行网络通信必须的五种信息 :连接协议 ; 本地端口号 ; 本地IP ;远程端口号 ; 远程ip;
在传输中,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 .为了区分不同应用进程和连接,因此提供了套接字(Socket) .通过Socket来区分不同应用连接通讯.
TPC/IP协议是传输层协议,主要解决数据 如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
TCP: 面向连接,可靠的字节流,点对点,传输大量数据,速度慢;
UDP: 不面向连接,易丢失数据不可靠,传输少量数据,速度快;
为什么UDP有时比TCP更有优势?
因为UDP简单传输快的优势,在越来越多场景下取代了TCP(游戏)
HTTP/HTTPS区别
https 需要申请证书,缴费 加密 端口是443 安全的端口协议
http: 不要虚申请证书,超文本协议,不加密 端口是80 不安全
HTTP Request请求: 请求行,请求头部,空行,请求数据四部分组成
请求行: 说明是GRT还是POST请求.
请求头部:
空行:
请求数据: 拼接在URL后面的keyvalue
HTTP 响应也由四部分组成,状态行,消息报头,空行,响应正文
响应正文:
200 ok 请求成功
400 BabeRequest 客户端请求语法错误,不能被服务端所理解
401 Unauthorized 请求未经授权,
403 Forbidden 服务端收到请求,但拒绝提供服务
404 Not Found 资源不存在
500 InternalServiceError 服务器发生了不可预期的错误
503 Service Unabalilable 服务器当前不能处理客户端的请求,一段时间后才能恢复
TCP/IP是个协议组,可分为四个层次:网络接口层、网络层、传输层和应用层。
在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议
在传输层中有TCP协议与UDP协议。
在**应用层有HTTP,**FTP、TELNET、SMTP、DNS等协议。
Http 超文本协议,最显著的特点就是客户端发送完请求需要服务端进行相应,服务端相应后就会释放连接, 从建立连接到关闭连接为一次连接,即短连接
TTPS是HTTP over SSL/TLS,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL/TLS:
SSL (Secure Socket Layer,安全套接字层)
TLS (Transport Layer Security,传输层安全协议)
SSL使用40 位关键字作为RC4流加密算法
socket 为了实现以上的通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议。
socket只是一种连接模式,不是协议,socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),
通过Socket,我们才能使用TCP/IP协议。tcp、udp,简单的说(虽然不准确)是两个最基本的协议
Socket 传输的特点:
优点
- 传输数据为字节级,传输数据可自定义,数据量小(对于手机应用讲:费用低)
2)传输数据时间短,性能高
3)适合于客户端和服务器端之间信息实时交互
4)可以加密,数据安全性强
缺点:
1)需对传输的数据进行解析,转化成应用级的数据
2)对开发人员的开发水平要求高
3)相对于Http协议传输,增加了开发量
客户端
创建socket对象,指定成对的服务端IP地址和端口号
通过socket获取输出流,写入数据发给服务端
通过socket获取输入流,接受服务端的发送的数据
关闭资源close
服务端(与服务端类似)
创建ServerSocket对象,并指定端口号,其端口号必须与客服端一致
通过ServerSocket对象,获取客户端的socket实例(ServerSocket.accept方法)
通过socket获取输入流,接受客户端发来的消息
通过socket获取输出流,写入数据向客户端发送数据作为回应
关闭资源
ps:
new Thread(new Runnable() {
@Override
public void run() {
//IO操作不能放在主线程执行
connectServer();
}
}).start();
private static void connectServer() {
try {
//1、创建客户端socket,指定服务端地址和端口
Socket socket = new Socket("localhost", 8888);
boolean connected = socket.isConnected(); //检查客户端与服务端是否连接成功
System.out.println(connected?"连接成功":"连接失败,请重试!");
//2、获取输出流,向服务器发送消息
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream),true);
writer.write("第一次来到广州\n");
writer.flush();
//3、获取输入流,并读取服务端的响应信息
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String info=reader.readLine();
System.out.println("客户端收到服务端回应:"+info);
//4、关闭资源
outputStream.close();
writer.close();
inputStream.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
服务端:
try {
//1、创建ServerSocket对象,指定与客户端一样的端口号
ServerSocket serverSocket = new ServerSocket(8888);
//2、获取Socket实例
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
//3、获取输入流,接受客户端发来的消息
InputStream inputStream = socket.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader reader = new BufferedReader(inputStreamReader);
String info=reader.readLine();
System.out.println("服务端收到客户端的信息: " +info);
//4、获取输出流,向客户端发送消息回应
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(outputStream);
writer.write("羊城欢迎你!"+"\n");
writer.flush();
//4、关闭IO资源
inputStream.close();
reader.close();
outputStream.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
使用write方法写入数据,字符串必须在中间或者末尾添加转义符\r或者\n,否则在使用readLine方法读取数据时,会一直阻塞从而读取失败。如下:
writer.write(“第一次来到广州\n”);
服务端
public class TCPServerService extends IntentService {
private static final String[] defaultMessages = {
"你好啊,嘻嘻",
"看了你相片,你好帅哦,很喜欢你这样的",
"我是江西人,你呢?",
"你在哪里工作?"};
private int index = 0;
private boolean isServiceDestroy = false;
//需注意,必须传入参数
public TCPServerService() {
super("TCP");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
//1、监听本地端口号
ServerSocket serverSocket = new ServerSocket(8954);
Socket socket = serverSocket.accept();
//2、获取输入流,接受用户发来的消息(Activity)
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//3、获取输出流,向客户端(Activity)回复消息
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream));
//4、通过循环不断读取客户端发来的消息 ,并发送
while (!isServiceDestroy) {
String readLine = reader.readLine();
if (!TextUtils.isEmpty(readLine)) {
String sendMag = index < defaultMessages.length ? defaultMessages[index] : "已离线";
SystemClock.sleep(500); //延迟发送
writer.println(sendMag+"\r"); // `\r或\n`必须要有,否则会影响客户端接受消息
writer.flush(); //刷新流
index++;
}
}
//关闭流
inputStream.close();
reader.close();
outputStream.close();
writer.close();
socket.close();
//需关闭,否则再次连接时,会报端口号已被使用
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
isServiceDestroy = true;
Log.d(TAG, "onDestroy: ");
}
private static final String TAG = "TCPServerService";
}
客户端:
public class SocketActivity extends AppCompatActivity {
private TextView mTvChatContent;
private EditText mEtSendContent;
private Intent mIntent;
private static final int CONNECT_SERVER_SUCCESS = 0; //与服务端连接成功
private static final int MESSAGE_RECEIVE_SUCCESS = 1; //接受到服务端的消息
private static final int MESSAGE_SEND_SUCCESS=2; //消息发送
@SuppressLint("all")
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case CONNECT_SERVER_SUCCESS:
//与服务端连接成功
mTvChatContent.setText("与聊天室连接成功\n");
break;
case MESSAGE_RECEIVE_SUCCESS:
String msgContent = mTvChatContent.getText().toString();
mTvChatContent.setText(msgContent+msg.obj.toString()+"\n");
break;
case MESSAGE_SEND_SUCCESS:
mEtSendContent.setText("");
mTvChatContent.setText(mTvChatContent.getText().toString()+msg.obj.toString()+"\n");
break;
}
return false;
}
});
private PrintWriter mPrintWriter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket);
mTvChatContent = findViewById(R.id.tv_chat_content);
mEtSendContent = findViewById(R.id.et_send_content);
//启动服务
mIntent = new Intent(this, TCPServerService.class);
startService(mIntent);
new Thread(new Runnable() {
@Override
public void run() {
//连接服务端,实现通信交互
//IO操作必须放在子线程执行
connectTCPServer();
}
}).start();
}
private Socket mSocket=null;
private void connectTCPServer() {
//通过循环来判断Socket是否有被创建,若没有则会每隔1s尝试创建,目的是保证客户端与服务端能够连接
while (mSocket == null) {
try {
//创建Socket对象,指定IP地址和端口号
mSocket = new Socket("localhost", 8954);
mPrintWriter = new PrintWriter(new OutputStreamWriter(mSocket.getOutputStream()),true);
if (mSocket.isConnected()) //判断是否连接成功
mHandler.sendEmptyMessage(CONNECT_SERVER_SUCCESS);
} catch (IOException e) {
e.printStackTrace();
//设计休眠机制,每次重试的间隔时间为1s
SystemClock.sleep(1000);
}
}
//通过循环来,不断的接受服务端发来的消息
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
while (!SocketActivity.this.isFinishing()){ //当Activity销毁后将不接受
String msg = reader.readLine();
if (!TextUtils.isEmpty(msg)){
//发消息通知更新UI
mHandler.obtainMessage(MESSAGE_RECEIVE_SUCCESS,msg).sendToTarget();
}
}
//关闭流
mPrintWriter.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressLint("SetTextI18n")
public void onClick(View view) {
switch (view.getId()) {
case R.id.but_send:
//必须开启子线程,不能在UI线程操作网络
new Thread(new Runnable() {
@Override
public void run() {
String msg = mEtSendContent.getText().toString();
if (mPrintWriter!=null && !TextUtils.isEmpty(msg)){
mPrintWriter.println(msg+"\n");
//此处可以不用刷新流的方法,因为在创建mPrintWriter对象时,在其构造方法中设置了自动刷新缓存
// mPrintWriter.flush();
//通知更新UI
mHandler.obtainMessage(MESSAGE_SEND_SUCCESS,msg).sendToTarget();
}
}
}).start();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//关闭输入流和连接
if (mSocket!=null){
try {
mSocket.shutdownInput();
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//停止后台服务
stopService(mIntent);
}
private static final String TAG = "TCPServerService";
}
Mainfest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<service android:name=".socket.TCPServerService"
android:exported="true"
android:process=":socket"/>