TCP协议,UDP,以及TCP通信服务器的文件传输
TCP通信过程
下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。
在这个例子中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序。注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。
三次握手 建立连接
建立连接(三次握手)的过程:
- 客户端发送一个带SYN标志的TCP报文到服务器。这是上图中三次握手过程中的段1。客户端发出SYN位表示连接请求。序号是1000,这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况。
另外,规定SYN位和FIN位也要占一个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该用序号1001。
mss表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大长度,就必须在IP层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。
- 服务器端回应客户端,是三次握手中的第2个报文段,同时带ACK标志和SYN标志。表示对刚才客户端SYN的回应;同时又发送SYN给客户端,询问客户端是否准备好进行数据通讯。
服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。
- 客户必须再次回应服务器端一个ACK报文,这是报文段3。
客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出。
因此一共有三个段用于建立连接,称为“三方握手”。在建立连接的同时,双方协商了一些信息,例如,双方发送序号的初始值、最大段尺寸等。
数据传输的过程:
- 客户端发出段4,包含从序号1001开始的20个字节数据。
- 服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据。
- 客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序号8011开始的数据。
在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。
总结:
3次握手:
1、主动: 发送 SYN 标志位。
2、被动:接收 SYN、同时回复 ACK 并且发送SYN
3、主动: 发送 ACK 标志位。 ―――――― Accpet() / Dial()
四次挥手
关闭连接(四次握手)的过程:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
- 客户端发出段7,FIN位表示关闭连接的请求。
- 服务器发出段8,应答客户端的关闭连接请求。
- 服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。
- 客户端发出段10,应答服务器的关闭连接请求。
建立连接的过程是三次握手,而关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止。
总结:
4次挥手:
1、主动关闭连接:发送 FIN 标志位。
2、被动关闭连接:接收 FIN、同时回复 ACK ―― 半关闭完成。
3、被动关闭连接:发送 FIN 标志位。
4、主动关闭连接:接收 FIN、同时回复 ACK ―― Close()/Close() ―― 4次挥手完成。
TCP状态转换
TCP状态图很多人都知道,它对排除和定位网络或系统故障时大有帮助。如果能熟练掌握这张图,了解图中的每一个状态,能大大提高我们对于TCP的理解和认识。下面对这张图的11种状态详细解析一下,以便加强记忆!不过在这之前,一定要熟练掌握TCP建立连接的三次握手过程,以及关闭连接的四次挥手过程。
CLOSED:表示初始状态。
LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。
SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。
ESTABLISHED:表示连接已经建立。
FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:
FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。
FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。
FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以 close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。
LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。
2MSL (Maximum Segment Lifetime) 和与之对应的TIME_WAIT状态,可以让4次握手关闭流程更加可靠。4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。注意,TIME_WAIT状态一定出现在主动关闭这一方。
总结:
TCP状态转换:
1. 主动端:
CLOSE --> SYN --> SYN_SEND状态 --> ESTABLISHED状态(数据通信期间处于的状态) ---> FIN --> FIN_WAIT_1状态。
---> 接收 ACK ---> FIN_WAIT_2状态 (半关闭―― 只出现在主动端) ---> 接收FIN、回ACK ――> TIME_WAIT (等2MSL)
---> 确保最后一个ACK能被对端收到。(只出现在主动端)
2. 被动端:
CLOSE --> LISTEN ---> ESTABLISHED状态(数据通信期间处于的状态) ---> 接收 FIN、回复ACK -->
CLOSE_WAIT(对应 对端处于 半关闭) --> 发送FIN --> LAST_ACK ---> 接收ACK ---> CLOSE
查看状态命令:
windows:netstat -an | findstr 8001(端口号)
Linux: netstat -an | grep 8001
UDP通信
UDP服务器
由于UDP是“无连接”的,所以,服务器端不需要额外创建监听套接字,只需要指定好IP和port,然后监听该地址,等待客户端与之建立连接,即可通信。
创建监听地址:
func ResolveUDPAddr(network, address string) (*UDPAddr, error)
创建用户通信的socket:
func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)
接收udp数据:
func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error)
写出数据到udp:
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
服务端完整代码实现如下:
UDP简单服务器:
1. 获取 服务器的 UDP地址结构体 srvAddr := ResolveUDPAddr(“udp”,“IP+port”)
2. 创建 用于数据通信套接字。 conn := ListenUDP("udp", srvAddr )
3. 读取客户端发送数据。 n, cltAddr, err := conn.ReadFromUDP(buf)
4. 回写数据给客户端。 conn.WriteToUDP("数据内容", cltAddr )
package main import (
"fmt"
"net"
) func main() {
//创建监听的地址,并且指定udp协议
udp_addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8002")
if err != nil {
fmt.Println("ResolveUDPAddr err:", err)
return
}
conn, err := net.ListenUDP("udp", udp_addr) //创建数据通信socket
if err != nil {
fmt.Println("ListenUDP err:", err)
return
}
defer conn.Close() buf := make([]byte, )
n, raddr, err := conn.ReadFromUDP(buf) //接收客户端发送过来的数据,填充到切片buf中。
if err != nil {
return
}
fmt.Println("客户端发送:", string(buf[:n])) _, err = conn.WriteToUDP([]byte("nice to see u in udp"), raddr) // 向客户端发送数据
if err != nil {
fmt.Println("WriteToUDP err:", err)
return
}
}
UDP客户端
udp客户端的编写与TCP客户端的编写,基本上是一样的,只是将协议换成udp。注意只能使用小写。
UDP客户端:
与TCP通信客户端实现手法一致。
net.Dial("udp", server 的IP+port)
代码如下:
package main import (
"net"
"fmt"
) func main() {
conn, err := net.Dial("udp", "127.0.0.1:8002")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close() conn.Write([]byte("Hello! I'm client in UDP!")) buf := make([]byte, )
n, err1 := conn.Read(buf)
if err1 != nil {
return
}
fmt.Println("服务器发来:", string(buf[:n]))
}
并发
其实对于UDP而言,服务器不需要并发,只要循环处理客户端数据即可。客户端也等同于TCP通信并发的客户端。
UDP并发服务器: ―――― UDP 默认支持并发。
1. 获取 服务器的 UDP地址结构体 srvAddr := ResolveUDPAddr(“udp”,“IP+port”)
2. 创建 用于数据通信套接字。 conn := ListenUDP("udp", srvAddr )
3. for 循环 读取客户端发送的数据 for {
n, cltAddr, err := conn.ReadFromUDP(buf)
}
4. 创建 go 程 完成 写操作,提高程序的并行效率。
go func() {
conn.WriteToUDP("数据内容", cltAddr )
}()
5.由于UDP没有建立连接过程。所以 TCP 通信状态 对于 UDP 无效。
服务器:
package main import (
"net"
"fmt"
) func main() {
// 创建 服务器 UDP 地址结构。指定 IP + port
laddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8003")
if err != nil {
fmt.Println("ResolveUDPAddr err:", err)
return
}
// 监听 客户端连接
conn, err := net.ListenUDP("udp", laddr)
if err != nil {
fmt.Println("net.ListenUDP err:", err)
return
}
defer conn.Close() for {
buf := make([]byte, )
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("conn.ReadFromUDP err:", err)
return
}
fmt.Printf("接收到客户端[%s]:%s", raddr, string(buf[:n])) conn.WriteToUDP([]byte("I-AM-SERVER"), raddr) // 简单回写数据给客户端
}
}
客户端:
UDP并发客户端:
并发读取 键盘 和 conn。 编码实现参考 TCP 并发客户端实现。
修改内容: net.Dial("udp", server 的IP+port)
package main import (
"net"
"os"
"fmt"
) func main() {
conn, err := net.Dial("udp", "127.0.0.1:8003")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
go func() {
str := make([]byte, )
for {
n, err := os.Stdin.Read(str) //从键盘读取内容, 放在str
if err != nil {
fmt.Println("os.Stdin. err1 = ", err)
return
}
conn.Write(str[:n]) // 给服务器发送
}
}()
buf := make([]byte, )
for {
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("服务器写来:", string(buf[:n]))
}
}
UDP与TCP的差异
TCP |
UDP |
面向连接 |
面向无连接 |
要求系统资源较多 |
要求系统资源较少 |
TCP程序结构较复杂 |
UDP程序结构较简单 |
使用流式 |
使用数据包式 |
保证数据准确性 |
不保证数据准确性 |
保证数据顺序 |
不保证数据顺序 |
通讯速度较慢 |
通讯速度较快 |
文件传输
网络文件传输:思路
发送端:(client)
1. 建立连接请求 net.Dial() ――> conn defer conn.Close()
2. 通过命令行参数,提取 文件名(带路径) os.Args
3. 获取文件属性 ,提取 文件名(不带路径)os.Stat()
4. 发送文件名 给 接收端 conn.Write
5. 接收对端回发的数据,确认是否是“ok”
6. 发送文件内容 给 接收端。封装 sendFile(文件名, conn) 函数
1) 只读方式打开 待发送文件
2) 创建 buf 读文件,存入buf中
3) 借助 conn 写 buf中的 数据到 接收端 ―― 读多少、写多少。
4) 判断文件读取、发送完毕。结束 conn 。断开连接。
接收端:(sever)
1. 创建监听套接字 listener := net.Listen()
2. 阻塞等待客户端连接请求。 conn = listener.Accept()
3. 读取发送端发送的文件名(不含路径)-- 保存
4. 回复“ok”给发送端。
5. 接收文件内容,保存成一个新文件。封装 RecvFile (文件名, conn) 函数
1) os.Create() 按文件名创建文件。 -- f
2) 从 conn 中读取文件内容。
3) 使用 f 写到本地新建文件中。 ―― 读多少、写多少
4) 判断文件读取完毕。结束 conn 。断开连接。
首先获取文件名。借助os包中的stat()函数来获取文件属性信息。在函数返回的文件属性中包含文件名和文件大小。Stat参数name传入的是文件访问的绝对路径。FileInfo中的Name()函数可以将文件名单独提取出来。
func Stat(name string) (FileInfo, error)
type FileInfo interface {
Name() string
Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
获取文件属性示例:
package main import (
"os"
"fmt"
) func main() {
list := os.Args // 获取命令行参数,存入list中
if len(list) != { // 确保用户输入了一个命令行参数
fmt.Println("格式为:xxx.go 文件名")
return
}
fileName := list[] // 从命令行保存文件名(含路径) fileInfo, err := os.Stat(fileName) //根据文件名获取文件属性信息 fileInfo
if err != nil {
fmt.Println("os.Stat err:", err)
return
}
fmt.Println("文件name为:", fileInfo.Name()) // 得到文件名(不含路径)
fmt.Println("文件size为:", fileInfo.Size()) // 得到文件大小。单位字节
}
客户端实现:
package main import (
"fmt"
"os"
"net"
"io"
) func SendFile(path string, conn net.Conn) {
// 以只读方式打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("os.Open err:", err)
return
}
defer f.Close() // 发送结束关闭文件。 // 循环读取文件,原封不动的写给服务器
buf := make([]byte, )
for {
n, err := f.Read(buf) // 读取文件内容到切片缓冲中
if err != nil {
if err == io.EOF {
fmt.Println("文件发送完毕")
} else {
fmt.Println("f.Read err:", err)
}
return
}
conn.Write(buf[:n]) // 原封不动写给服务器
}
} func main() {
// 提示输入文件名
fmt.Println("请输入需要传输的文件:")
var path string
fmt.Scan(&path) // 获取文件名 fileInfo.Name()
fileInfo, err := os.Stat(path)
if err != nil {
fmt.Println("os.Stat err:", err)
return
} // 主动连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8005")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close() // 给接收端,先发送文件名
_, err = conn.Write([]byte(fileInfo.Name()))
if err != nil {
fmt.Println("conn.Write err:", err)
return
} // 读取接收端回发确认数据 —— ok
buf := make([]byte, )
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
} // 判断如果是ok,则发送文件内容
if "ok" == string(buf[:n]) {
SendFile(path, conn) // 封装函数读文件,发送给服务器,需要path、conn
}
}
客户端
package main
import (
"net"
"fmt"
"os"
"io"
)
func filesend(filepath string,conn net.Conn){
buf:=make([]byte,)
f1,err:=os.OpenFile(filepath,os.O_RDONLY,)
if err!=nil{
fmt.Println("打开文件错误",err)
return
}
defer f1.Close()
for {
n, err := f1.Read(buf)
if err != nil {
if err ==io.EOF{
fmt.Println("读取完毕")
break
}else{
fmt.Println("read err", err)
return
}
}
_, err = conn.Write(buf[:n])
if err != nil {
if err==io.EOF{
fmt.Println("文件发送完毕")
break
}
fmt.Println("发送err", err)
return
}
}
}
func main() {
list:=os.Args
filepath:=list[]
fileinfo,err:=os.Stat(filepath)
if err!=nil{
fmt.Println("stat err",err)
return
}
str:=fileinfo.Name()
//fmt.Println(str)
buf:=make([]byte,)
conn,err:=net.Dial("tcp","127.0.0.1:8000")
if err!=nil{
fmt.Println("conn err",err)
return
}
defer conn.Close()
n,err:=conn.Write([]byte(str))
if err!=nil{
fmt.Println("write err",err)
return
}
fmt.Printf("发送的文件名%q",string(buf[:n]))
//buf2:=make([]byte,4096)
n,err=conn.Read(buf)
if err!=nil{
fmt.Println("服务器发来错误",err)
return
}
if string(buf[:n])=="ok"{
fmt.Println("服务器接收成功")
filesend(filepath,conn)
}
}
自己的思路
服务端实现:
package main import (
"net"
"fmt"
"os"
"io"
) func RecvFile(fileName string, conn net.Conn) {
// 创建新文件
f, err := os.Create(fileName)
if err != nil {
fmt.Println("Create err:", err)
return
}
defer f.Close() // 接收客户端发送文件内容,原封不动写入文件
buf := make([]byte, )
for {
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("文件接收完毕")
} else {
fmt.Println("Read err:", err)
}
return
}
f.Write(buf[:n]) // 写入文件,读多少写多少
}
} func main() {
// 创建监听
listener, err := net.Listen("tcp", "127.0.0.1:8005")
if err != nil {
fmt.Println("Listen err:", err)
return
}
defer listener.Close() // 阻塞等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err:", err)
return
}
defer conn.Close() // 读取客户端发送的文件名
buf := make([]byte, )
n, err := conn.Read(buf)
if err != nil {
fmt.Println("Read err:", err)
return
}
fileName := string(buf[:n]) // 保存文件名 // 回复 0k 给发送端
conn.Write([]byte("ok")) // 接收文件内容
RecvFile(fileName, conn) // 封装函数接收文件内容, 传fileName 和 conn
}
服务端
package main
import (
"net"
"fmt"
"os"
"io"
)
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("listener err", err)
return
}
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
fmt.Println("conn err", err)
return
}
defer conn.Close()
buf := make([]byte, )
n, err := conn.Read(buf)
if err != nil {
fmt.Println("read err", )
return
}
pathname := string(buf[:n])
fmt.Println(pathname)
_, err = conn.Write([]byte("ok"))
if err != nil {
fmt.Println("write err", err)
return
}
recvfile(pathname,conn) }
func recvfile(pathname string,conn net.Conn){
str:="D:/1/"+pathname
fmt.Println(str)
f1,err:=os.Create(str)
if err!=nil{
fmt.Println("create err",err)
return
}
defer f1.Close()
buf:=make([]byte,)
for {
n,err:=conn.Read(buf)
if err!=nil{
if err==io.EOF{
fmt.Println("文件接收完毕")
break
}
fmt.Println("conn read err",err)
break
}
f1.Write(buf[:n])
} }
自己的思路
小知识
获取命令行参数:
os.Args 提取命令行参数,保存成 []string
使用格式: go run xxx.go arg1 arg2 arg3 arg4 ...
获取命令行参数:
arg[0]: xxx.go ――> xxx.exe 的绝对路径
arg[1]: arg1
arg[2]: arg2
arg[3]: arg3
....
获取文件属性:
os.Stat(文件访问绝对路径) ――> fileInfo interface { Name() Size() }
提取文件 不带路径的“文件名”
TCP协议,UDP,以及TCP通信服务器的文件传输的更多相关文章
- JAVA 基于TCP协议的一对一,一对多文件传输实现
最近老师给我们上了多线程和TCP和UDP协议,其中一个要求就是我们用JAVA协议一个基于TCP和UDP这两种协议的一对一文件上传和一对多文件上传. 然后我就开始分析TCP和UDP这两个协议的特点,发现 ...
- Android程序员必知必会的网络通信传输层协议——UDP和TCP
1.点评 互联网发展至今已经高度发达,而对于互联网应用(尤其即时通讯技术这一块)的开发者来说,网络编程是基础中的基础,只有更好地理解相关基础知识,对于应用层的开发才能做到游刃有余. 对于Android ...
- java 网络通信传输层协议——UDP和TCP
本文原文由作者“zskingking”发表于:jianshu.com/p/271b1c57bb0b,本次收录有改动. 1.点评 互联网发展至今已经高度发达,而对于互联网应用(尤其即时通讯网专注的即时通 ...
- Python进阶----网络通信基础 ,OSI七层协议() ,UDP和TCP的区别 , TCP/IP协议(三次握手,四次挥手)
Python进阶----网络通信基础 ,OSI七层协议() ,UDP和TCP的区别 , TCP/IP协议(三次握手,四次挥手) 一丶CS/BS 架构 C/S: 客户端/服务器 定义: ...
- 5┃音视频直播系统之 WebRTC 中的协议UDP、TCP、RTP、RTCP详解
一.UDP/TCP 如果让你自己开发一套实时互动直播系统,在选择网络传输协议时,你会选择使用UDP协议还是TCP协议 假如使用 TCP 会怎样呢?在极端网络情况下,TCP 为了传输的可靠性,将会进行反 ...
- 网络编程(二)——TCP协议、基于tcp协议的套接字socket
TCP协议与基于tcp协议的套接字socket 一.TCP协议(流式协议) 1.可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的 ...
- 【TCP协议】(3)---TCP粘包黏包
[TCP协议](3)---TCP粘包黏包 有关TCP协议之前写过两篇博客: 1.[TCP协议](1)---TCP协议详解 2.[TCP协议](2)---TCP三次握手和四次挥手 一.TCP粘包.拆包图 ...
- TCP协议,UDP协议
帅爆太阳的男人 1,TCP协议 回环地址:127.0.0.1(所有电脑都这一个默认回环地址)每个计算机都有这么一个本机地址只能被本机识别,不会被其他机器识别(因为你用这个地址传内容他就传不出去) TC ...
- TCP/IP协议 | TCP协议 | UDP协议 | 三次握手四次挥手
TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP.SMTP.TCP.UDP.IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP ...
随机推荐
- Verify_Execute 验证SQL语句执行结果
#region Verify_Execute /// <summary> /// 验证insert.update.delete执行 /// </summary> /// < ...
- DotNetCore 部署到IIS 上
将 DotNetCore MVC 项目成功部署到 IIS 上,记录下配置要点: 1.在 ASP.NET Core 应用中使用 Kestrel Microsoft.AspNetCore.App 元包中包 ...
- tensorflow学习笔记1:导出和加载模型
用一个非常简单的例子学习导出和加载模型: 导出 写一个y=a*x+b的运算,然后保存graph: import tensorflow as tf from tensorflow.python.fram ...
- go语言入门教程:基本语法之变量声明及注意事项
一.变量的使用 1.1 什么是变量 变量是为存储特定类型的值而提供给内存位置的名称.在go中声明变量有多种语法. 所以变量的本质就是一小块内存,用于存储数据,在程序运行过程中数值可以改变 1.2 声明 ...
- Java SE中的Synchronized
1 引言 在多线程并发的编程中Synchronized一直是元老级的角色,很多人会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化以后,有些情况下它并不那么重了. ...
- c#检测是否存在数据库(SQL SERVER)
private static bool CheckDatabaseExists( string databaseName) { { string sqlCreateDBQuery; bool resu ...
- [Python]数据挖掘(1)、梯度下降求解逻辑回归——考核成绩分类
ps:本博客内容根据唐宇迪的的机器学习经典算法 学习视频复制总结而来 http://www.abcplus.com.cn/course/83/tasks 逻辑回归 问题描述:我们将建立一个逻辑回归模 ...
- Session &cookie introduction,usage
Cookie 1)什么是Cookie? 服务器为了识别用户身份而临时存放在浏览器端的少量数据. 2)工作原理 浏览器访问服务器时,服务器将一些数据以set-cooki ...
- TCP/IP学习
1.TCP/IP网络包括两部分 ①传输协议 ②网络协议
- Java基础学习-三元运算符和键盘录入的基本步骤和使用
1.三元运算符的执行流程 package com.denniscui_05; /* * 三元运算符: * 关系表达式?表达式1:表达式2 * * 执行流程: * ...