网络上的两个程序经过一个双向的通讯链接实现数据的交换,这个链接的一端称为一个socket
。
创建网络通讯链接至少要一对端口号(socket
)。socket本质是编程接口(API
),对TCP/IP
的封装,TCP/IP
也要提供可供程序员作网络开发所用的接口,这就是Socket编程接口
;HTTP是轿车
,提供了封装或者显示数据的具体形式;Socket是发动机
,提供了网络通讯的能力。
Socket的英文原义是“孔”或“插座”。做为BSD UNIX的进程通讯机制,取后一种意思。一般也称做"套接字",用于描述IP地址和端口,是一个通讯链的句柄,能够用来实现不一样虚拟机或不一样计算机之间的通讯。每种服务都打开一个Socket,并绑定到一个端口上,不一样的端口对应于不一样的服务。Socket正如其英文原意那样,像一个多孔插座。插座是用来给插头提供一个接口让其通电的,此时咱们就能够将插座当作一个服务端,不一样的插头当作客户端。程序员
net.Dial("tcp",addr)
func main() { addr := "wwww.baidu.com:80" //定义主机名 conn,err := net.Dial("tcp",addr) //拨号操做,须要指定协议。 if err != nil { log.Fatal(err) } fmt.Println(conn.RemoteAddr().String()) //220.181.57.217:80 fmt.Println(conn.LocalAddr()) //192.168.0.120:62662 fmt.Println(reflect.TypeOf(conn.LocalAddr())) //*net.TCPAddr fmt.Println(reflect.TypeOf(conn.RemoteAddr())) n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服务端发送数据。用n接受返回的数据大小。 if err != nil { log.Fatal(err) } fmt.Println("写入的大小是:",n)//18 buf := make([]byte,10) //定义一个切片的长度是1024。 for { n,err = conn.Read(buf) //接收到的内容大小。 if err == io.EOF { conn.Close() } fmt.Print(string(buf[:n])) } fmt.Println(string(buf[:n])) //将接受的内容都读取出来。 }
r := bufio.NewReader(conn) //将这个连接(connection)包装如下。将conn的内容都放入r中,可是没有进行读取 for { line,err := r.ReadString('\n') //将r的内容也就是conn的数据按照换行符进行读取。 if err == io.EOF { conn.Close() } fmt.Print(line) }
io.Copy(os.Stdout,conn)
package main import ( "net" "log" "time" ) func Handle_conn(conn net.Conn) { //这个是在处理客户端会阻塞的代码。 //var buf = make([]byte, 10) //读取信息 //n, err := c.Read(buf) conn.Write([]byte("xxxxx\n")) //经过conn的wirte方法将这些数据返回给客户端。 conn.Write([]byte("hello world!\n")) time.Sleep(time.Minute) conn.Close() //与客户端断开链接。 } func main() { addr := "0.0.0.0:8080" //表示监听本地全部ip的8080端口,也能够这样写:addr := ":8080" listener,err := net.Listen("tcp",addr) if err != nil { log.Fatal(err) } defer listener.Close() for { conn,err := listener.Accept() //用conn接收连接 if err != nil { log.Fatal(err) } go Handle_conn(conn) //开启多个协程。 } }
对方服务器很忙,瞬间有大量client端链接尝试向server创建,server端的listen backlog队列满,server accept不及时((即使不accept,那么在backlog数量范畴里面,connect都会是成功的,由于new conn已经加入到server side的listen queue中了,accept只是从queue中取出一个conn而已),这将致使client端Dial阻塞。咱们仍是经过例子感觉Dial的行为特色:编程
链接创建后,若是对方未发送数据到socket,接收方(Server)会阻塞在Read操做上,这和前面提到的“模型”原理是一致的。执行该Read操做的goroutine也会被挂起。runtime会监视该socket,直到其有数据才会从新
调度该socket对应的Goroutine完成read。缓存
若是socket中有部分数据,且长度小于一次Read操做所指望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等待全部指望数据所有读取后再返回。服务器
TCP链接通讯两端的OS都会为该链接保留数据缓冲,一端调用Write后,实际上数据是写入到OS的协议栈的数据缓冲的。TCP是全双工通讯,所以每一个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞。网络
若是非要给Write增长一个期限,那咱们能够调用SetWriteDeadline方法。咱们copy一份client5.go,造成client6.go,在client6.go的Write以前增长一行timeout设置代码:socket
conn.SetWriteDeadline(time.Now().Add(time.Microsecond * 10))
ICMP是用来对网络情况进行反馈的协议,能够用来侦测网络状态或检测网路错误。
参考文章tcp
func main() { //构建服务器链接 conn,_:=net.ResolveTCPAddr("tcp","127.0.0.1:1234") //链接拨号 n,_:=net.DialTCP("tcp",nil,conn) //发送数据 n.Write([]byte("hello kongyixueyuan")) fmt.Println("发送结束") }
func main() { //监听电脑中的端口,1024-65535 netListen,_:=net.Listen("tcp","127.0.0.1:1234") //延时关闭 defer netListen.Close() //经过循环等待客户端的链接 for { //只有客户端链接成功才会向下执行 conn,_:=netListen.Accept() //建立缓存,存放客户端发送的数据 data:=make([]byte,1024) for { //接收客户端发送的数据,n数据的大小 n,_:=conn.Read(data) //data[:n]就是接受到的密文 fmt.Println("密文为:",data[:n]) break } } }