其实我前面一篇笔记的例子就是socket的一个例子,但是由于大部分的笔记说明都是在整理基础的东西,所以socket的笔记单独列在这里。
server.go
package socket
import (
"fmt"
"net"
)
func StartServer() {
service := ":3338"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
fmt.Println("新连接:", conn.RemoteAddr().String())
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
for {
buffer := make([]byte, 1024)
length, _:= conn.Read(buffer)
if length > 12 {
data := buffer[:length]
switch data[11] & 0xff {
case 0x80:
//桌子
fmt.Println("桌子")
case 0x90:
//椅子
case 0xA0:
//台灯
default:
//其它
}
//写数据
// conn.Write(data)
}
}
}
func isProtocol(data []byte) bool {
if (data[0]&0xff) == 0xC0 && (data[len(data)-1]&0xff) == 0xC1 {
return true
}
return false
}
func checkError(err error) {
if err != nil {
fmt.Println(err.Error())
}
}
和java一样,golang提供了比较强大的网络编程的基础库net。
当然我们想使用golang的网络相关的库,先导入net包。而fmt包,则是为了测试一些结果,作为终端输出的工具包。
import ( "fmt" "net" )
通过net包的ResolveTCPAddr函数声明一个TCPAddr。
// TCPAddr represents the address of a TCP end point.
type TCPAddr struct { IP IP Port int Zone string // IPv6 scoped addressing zone }
func ResolveTCPAddr(net, addr string) (*TCPAddr, error)
switch net {
case "tcp", "tcp4", "tcp6":
case "": // a hint wildcard for Go 1.0 undocumented behavior
net = "tcp"
default:
return nil, UnknownNetworkError(net)
}
从源码上看,ip是可以缺省的,但端口不能缺省。ip缺省的话,默认使用的是本地地址。ip和端口使用英文的冒号(:)分开,net包是通过冒号分隔字符串的方式来处理ResolveTCPAddr的net参数的。
ResolveTCPAddr可供服务器及客户端共用的,不像java那样区分Socket和ServerSocket。所以缺省ip的逻辑很好理解。
我们这里写的是服务器端的逻辑,只输入端口号。
// SplitHostPort splits a network address of the form "host:port", // "[host]:port" or "[ipv6-host%zone]:port" into host or // ipv6-host%zone and port. A literal address or host name for IPv6 // must be enclosed in square brackets, as in "[::1]:80", // "[ipv6-host]:http" or "[ipv6-host%zone]:80".
func SplitHostPort(hostport string) (host, port string, err error) {
j, k := 0, 0
// The port starts after the last colon.
i := last(hostport, ':')
if i < 0 {
goto missingPort
}
if hostport[0] == '[' {
// Expect the first ']' just before the last ':'.
end := byteIndex(hostport, ']')
if end < 0 {
err = &AddrError{Err: "missing ']' in address", Addr: hostport}
return
}
switch end + 1 {
case len(hostport):
// There can't be a ':' behind the ']' now.
goto missingPort
case i:
// The expected result.
default:
// Either ']' isn't followed by a colon, or it is
// followed by a colon that is not the last one.
if hostport[end+1] == ':' {
goto tooManyColons
}
goto missingPort
}
host = hostport[1:end]
j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions
} else {
host = hostport[:i]
if byteIndex(host, ':') >= 0 {
goto tooManyColons
}
if byteIndex(host, '%') >= 0 {
goto missingBrackets
}
}
if byteIndex(hostport[j:], '[') >= 0 {
err = &AddrError{Err: "unexpected '[' in address", Addr: hostport}
return
}
if byteIndex(hostport[k:], ']') >= 0 {
err = &AddrError{Err: "unexpected ']' in address", Addr: hostport}
return
}
port = hostport[i+1:]
return
missingPort:
err = &AddrError{Err: "missing port in address", Addr: hostport}
return
tooManyColons:
err = &AddrError{Err: "too many colons in address", Addr: hostport}
return
missingBrackets:
err = &AddrError{Err: "missing brackets in address", Addr: hostport}
return
}
func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
这里的net参数也是支付”tcp”, “tcp4”, “tcp6”。但不支持空字符串。
func (l *TCPListener) Accept() (Conn, error)
整个过程和java socket server的思想基本是一至的。
type Conn interface { // Read reads data from the connection. // Read can be made to time out and return a Error with Timeout() == true // after a fixed time limit; see SetDeadline and SetReadDeadline. Read(b []byte) (n int, err error) // Write writes data to the connection. // Write can be made to time out and return a Error with Timeout() == true // after a fixed time limit; see SetDeadline and SetWriteDeadline. Write(b []byte) (n int, err error) // Close closes the connection. // Any blocked Read or Write operations will be unblocked and return errors. Close() error // LocalAddr returns the local network address. LocalAddr() Addr // RemoteAddr returns the remote network address. RemoteAddr() Addr // SetDeadline sets the read and write deadlines associated // with the connection. It is equivalent to calling both // SetReadDeadline and SetWriteDeadline. // // A deadline is an absolute time after which I/O operations // fail with a timeout (see type Error) instead of // blocking. The deadline applies to all future I/O, not just // the immediately following call to Read or Write. // // An idle timeout can be implemented by repeatedly extending // the deadline after successful Read or Write calls. // // A zero value for t means I/O operations will not time out. SetDeadline(t time.Time) error // SetReadDeadline sets the deadline for future Read calls. // A zero value for t means Read will not time out. SetReadDeadline(t time.Time) error // SetWriteDeadline sets the deadline for future Write calls. // Even if write times out, it may return n > 0, indicating that // some of the data was successfully written. // A zero value for t means Write will not time out. SetWriteDeadline(t time.Time) error }
fmt.Println("新连接:", conn.RemoteAddr().String())
注意:Conn接口的Read也是阻塞的。
func handleConn(conn net.Conn) {
defer conn.Close()
for {
...
}
}
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源,比如常见的关闭文件、释放缓存。
这里是操作完写操作后,就把conn连接关掉。
在Go中,每个并发处理的部分被称作 goroutines(协程),它可以进行更有效的并发运算。在协程和操作系统线程之间并无一对一的关系:协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在他们之上的;协程调度器在 Go 运行时很好的完成了这个工作。
golang想使用协程的方式很简单,只要在需要使用协程的方法调用的前面加上go关键字就可以了。
如:
go handleConn(conn)
这种方式相比java的线程,太优雅了。