网络通信过程

- DMA:网卡和磁盘数据拷贝到内存流程比较固定,不涉及到运算操作,且非常耗时。在磁盘嵌入一个DMA芯片,完成上述拷贝工作,把CPU解脱出来,让CPU专注于运算。
- mmap:用户空间和内核空间映射同一块内存空间,从而达到省略将数据从内核缓冲区拷贝到用户空间的操作,用户空间通过映射直接操作内核缓冲区的数据。
阻塞式网络I/O

非阻塞式网络I/O

多路复用网络I/O

socket把复杂的传输层协议封装成简单的接口,使应用层可以像读写文件一样进行网络数据的传输。

socket通信过程

TCP CS架构
网络通信模型
OSI参考模型

TCP/IP模型

传输层数据大小的上限为MSS(Maximum Segment Size, 最大分段大小),网络接口层数据大小的上限为MTU(Maximum Transmit Unit, 最大传输单元)。
TCP协议解读
MSS=MTU-ip首部-tcp首部,MTU视网络接口层的不同而不同。TCP在建立连接时通常需要协商双方的MSS值。应用层传输的数据大于MSS时需要分段。

TCP首部

- 前20个字节是固定的,后面还4N个可选字节(TCP选项)。
- 数据偏移:TCP数据部分距TCP开头的偏移量(一个偏移量是4个字节, TCP选项占4N个字节),亦即TCP首部的长度。所以TCP首部的最大长度是15*4=60个字节,即TCP选项最多有40个字节。
- 端口在tcp层指定,ip在IP层指定。端口占2个字节,则最大端口号为2^16-1=65535。
- 由于应用层的数据被分段了,为了在接收端对数据按顺序重组,需要为每段数据编个“序号”。
- TCP规定在连接建立后所有传送的报文段都必须把ACK设置为1。
TCP建立连接

- 第一次握手:TCP首部SYN=1,初始化一个序号=J。SYN报文段不能携带数据。
- 第二次握手:TCP首部SYN=1,ACK=1,确认号=J+1,初始化一个序号=K。此报文同样不携带数据。
- 第三次握手:SYN=1,ACK=1,序号=J+1,确认号=K+1。此次一般会携带真正需要传输的数据。
- 确认号:即希望下次对方发过来的序号值。
- SYN Flood 攻击始终不进行第三次握手,属于DDOS攻击的一种。
TCP释放连接

- TCP的连接是全双工(可以同时发送和接收)的连接,因此在关闭连接的时候,必须关闭传送和接收两个方向上的连接。
- 第一次挥手:FIN=1,序号=M。
- 第二次挥手:ACK=1,序号=M+1。
- 第三次挥手:FIN=1,序号=N。
- 第四次挥手:ACK=1,序号=N+1。
- 从TIME_WAIT进入CLOSED需要经过2个MSL(Maxinum Segment Lifetime),RFC793建议MSL=2分钟。
Go TCP编程
- 用三元给(ip地址,协议,端口号)唯一标示网络中的一个进程,如(172.122.121.111, tcp, 5656)。
- IPv4的地址位数为32位,分为4段,每段最大取值为255。
- IPv6的地址位数为128位,分为8段,各段用16进制表示,最大取值为ffff。
- 端口:0~1023被熟知的应用程序占用(普通应用程序不可以使用),49152~65535客户端程序运行时动态选择使用。
1
| func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
|
net参数是“tcp4”、“tcp6”、“tcp”中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,、IPv6的任意一个)。addr表示域名或者IP地址,例如" www.qq.com:80" 或者"127.0.0.1:22"。
1
| func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
|
network参数是"tcp4"、“tcp6”、"tcp"中的任意一个。laddr表示本机地址,一般设置为nil。raddr表示远程的服务地址。
1
| func net.DialTimeout(network string, address string, timeout time.Duration) (net.Conn, error)
|
创建连接时设置超时时间。
1
| func (*net.conn) Write(b []byte) (int, error)
|
通过conn发送数据。
1
| func (net.Conn).Read(b []byte) (n int, err error)
|
从conn里读取数据,如果没有数据可读,会阻塞。
1
| func ioutil.ReadAll(r io.Reader) ([]byte, error)
|
从conn中读取所有内容,直到遇到error(比如连接关闭)或EOF。
1
| func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
|
监听端口。
1
| func (l *TCPListener) Accept() (Conn, error)
|
阻塞,直到有客户端请求建立连接。
1
| func (*net.conn) Close() error
|
关闭连接。
1 2
| func (c *TCPConn) SetReadDeadline(t time.Time) error func (c *TCPConn) SetWriteDeadline(t time.Time) error
|
设置从一个tcp连接上读取和写入的超时时间。
1
| func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
|
当一个tcp连接上没有数据时,操作系统会间隔性地发送心跳包,如果长时间没有收到心跳包会认为连接已经断开。
tcp_server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package main
import ( "encoding/json" "fmt" "log" "net" "strconv" "time" )
type ( Request struct { A int B int } Response struct { Sum int } )
func handleRequest2(conn net.Conn) { conn.SetReadDeadline(time.Now().Add(30 * time.Second)) defer conn.Close() for { requestBytes := make([]byte, 256) read_len, err := conn.Read(requestBytes) if err != nil { fmt.Printf("read from socket error: %s\n", err.Error()) break } fmt.Printf("receive request %s\n", string(requestBytes[:read_len]))
var request Request json.Unmarshal(requestBytes[:read_len], &request) response := Response{Sum: request.A + request.B}
responseBytes, _ := json.Marshal(response) _, err = conn.Write(responseBytes) if err != nil { break } fmt.Printf("write response %s\n", string(responseBytes)) } }
func main() { ip := "127.0.0.1" port := 5656 tcpAddr, err := net.ResolveTCPAddr("tcp4", ip+":"+strconv.Itoa(port)) if err != nil { log.Fatal(err.Error()) } listener, err := net.ListenTCP("tcp4", tcpAddr) if err != nil { log.Fatal(err.Error()) } fmt.Println("waiting for client connection ......") for { conn, err := listener.Accept() if err != nil { continue } fmt.Printf("establish connection to client %s\n", conn.RemoteAddr().String()) go handleRequest2(conn) } }
|
tcp_client.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package main
import ( "encoding/json" "fmt" "log" "math/rand" "net" "time" )
type ( Request struct { A int B int } Response struct { Sum int } )
func main() { conn, err := net.DialTimeout("tcp4", "127.0.0.1:5656", 30*time.Minute) if err != nil { log.Fatal(err.Error()) } fmt.Printf("establish connection to server %s\n", conn.RemoteAddr().String()) defer conn.Close() for { rand.Seed(time.Now().UnixNano()) request := Request{A: rand.Intn(100), B: rand.Intn(100)} requestBytes, _ := json.Marshal(request) _, err = conn.Write(requestBytes) if err != nil { log.Fatal(err.Error()) } fmt.Printf("write request %s\n", string(requestBytes)) responseBytes := make([]byte, 256) read_len, err := conn.Read(responseBytes) if err != nil { log.Fatal(err.Error()) } var response Response json.Unmarshal(responseBytes[:read_len], &response) fmt.Printf("receive response: %d\n", response.Sum) time.Sleep(1 * time.Second) } }
|
UDP CS架构
UDP协议解读

- UDP首部占8个字节,所以UDP报文长度最小是8B。
- 不需要建立连接,直接收发数据,效率很高
- 面向报文。对应用层交下来的报文,既不合并也不拆分,直接加上边界交给IP层。TCP是面向字节流,TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送;如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。
- 从机制上不保证顺序(在IP层要对数据分段),可能会丢包(检验和如果出差错就会把这个报文丢弃掉)。在内网环境下分片乱序和数据丢包极少发生。
- 支持一对一、一对多、多对一和多对多的交互通信。
Go UDP编程
1
| func net.Dial(network string, address string) (net.Conn, error)
|
netwok指定为udp,建立udp连接(伪连接)。
1
| func net.DialTimeout(network string, address string, timeout time.Duration) (net.Conn, error)
|
netwok指定为udp,建立连接时指定超时。
1
| func net.ResolveUDPAddr(network string, address string) (*net.UDPAddr, error)
|
解析成udp地址。
1
| func net.ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error)
|
直接调用Listen就返回一个udp连接。
1
| func (*net.UDPConn).ReadFromUDP(b []byte) (int, *net.UDPAddr, error)
|
读数据,会返回remote的地址。
1
| func (*net.UDPConn).WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
|
写数据,需要指定remote的地址。
udp_server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package main
import ( "encoding/json" "fmt" "log" "net" "strconv" )
type ( Request struct { A int B int } Response struct { Sum int } )
func main() { ip := "127.0.0.1" port := 5656 udpAddr, err := net.ResolveUDPAddr("udp", ip+":"+strconv.Itoa(port)) if err != nil { log.Fatal(err.Error()) } conn, err := net.ListenUDP("udp", udpAddr) if err != nil { log.Fatal(err.Error()) } defer conn.Close() for { requestBytes := make([]byte, 256) read_len, remoteAddr, err := conn.ReadFromUDP(requestBytes) if err != nil { log.Printf("read from socket error: %s\n", err.Error()) break } fmt.Printf("receive request %s from %s\n", string(requestBytes), remoteAddr.String())
var request Request json.Unmarshal(requestBytes[:read_len], &request) response := Response{Sum: request.A + request.B}
responseBytes, _ := json.Marshal(response) _, err = conn.WriteToUDP(responseBytes, remoteAddr) if err != nil { log.Println(err) break } fmt.Printf("write response %s to %s\n", string(responseBytes), remoteAddr.String()) } }
|
udp_client.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package main
import ( "encoding/json" "fmt" "log" "math/rand" "net" "strconv" "sync" "time" )
type ( Request struct { A int B int } Response struct { Sum int } )
func main() { ip := "127.0.0.1" port := 5656 conn, err := net.DialTimeout("udp", ip+":"+strconv.Itoa(port), 30*time.Minute) if err != nil { log.Fatal(err.Error()) } defer conn.Close() const P = 10 wg := sync.WaitGroup{} wg.Add(P) rand.Seed(time.Now().UnixNano()) for i := 0; i < P; i++ { request := Request{A: rand.Intn(100), B: rand.Intn(100)} requestBytes, _ := json.Marshal(request) go func() { defer wg.Done() _, err = conn.Write(requestBytes) if err != nil { log.Println(err.Error()) return } fmt.Printf("write request %s\n", string(requestBytes)) responseBytes := make([]byte, 256) read_len, err := conn.Read(responseBytes) if err != nil { log.Println(err) return } var response Response json.Unmarshal(responseBytes[:read_len], &response) fmt.Printf("receive response: %d\n", response.Sum) time.Sleep(1 * time.Second) }() } wg.Wait() }
|
由于UDP不需要建立连接,所以通过Dial()创建的是一个虚拟连接, Dial()总是会立即返回成功,即使对方还没有准备好。所以UDP可以先启client,再启server。由于是虚拟连接所以多个client可以共用一个conn,所以Server端往conn里写数据时需要指定写给哪个client,同理从conn里读数据会返回client的Address,即WriteToUDP (b []byte, addr *net.UDPAddr)和ReadFromUDP(b []byte) (int, *net.UDPAddr, error)。由于UDP是无连接和,对方关闭连接后,本方再在conn上调用Write和Read不会报错。
应用层的一条完整数据称为报文。TCP是面向字节流的,一次Read到的数据可能包含了多个报文,也可能只包含了半个报文,一条报文在什么地方结束需要通信双方事先约定好。UDP是面向报文的,一次Read只读一个报文,如果没有把一个报文读完,后面的内容会被丢弃掉,下次就读不到了。