Linux下Golang Socket编程原理分析与代码实现
在POSIX标准推出后,socket在各大主流OS平台上都得到了很好的支持。而Golang是自带Runtime的跨平台编程语言,Go中提供给开发者的Socket API是建立在操作系统原生Socket接口之上的。但Golang 中的Socket接口在行为特点与操作系统原生接口有一些不同。本文将结合一个简单的网络聊天程序加以分析。
一、socket简介
首先进程之间可以进行通信的前提是进程可以被唯一标识,在本地通信时可以使用PID唯一标识,而在网络中这种方法不可行,我们可以通过IP地址+协议+端口号来唯一标识一个进程,然后利用socket进行通信。socket通信流程如下:
1.服务端创建socket
2.服务端绑定socket和端口号
3.服务端监听该端口号
4.服务端启动accept()用来接收来自客户端的连接请求,此时如果有连接则继续执行,否则将阻塞在这里。
5.客户端创建socket
6.客户端通过IP地址和端口号连接服务端,即tcp中的三次握手
7.如果连接成功,客户端可以向服务端发送数据
8.服务端读取客户端发来的数据
9.任何一端均可主动断开连接
二、socket编程
有了抽象的socket后,当使用TCP或UDP协议进行web编程时,可以通过以下的方式进行。
服务端伪代码:
listenfd = socket(……)
bind(listenfd, ServerIp:Port, ……)
listen(listenfd, ……)
while(true) {
conn = accept(listenfd, ……)
receive(conn, ……)
send(conn, ……)
}
客户端伪代码:
clientfd = socket(……)
connect(clientfd, serverIp:Port, ……)
send(clientfd, data)
receive(clientfd, ……)
close(clientfd)
上述伪代码中,listenfd就是为了实现服务端监听创建的socket描述符,而bind方法就是服务端进程占用端口,避免其它端口被其它进程使用,listen方法开始对端口进行监听。下面的while循环用来处理客户端源源不断的请求,accept方法返回一个conn,用来区分各个客户端的连接的,之后的接受和发送动作都是基于这个conn来实现的。其实accept就是和客户端的connect一起完成了TCP的三次握手。
三、golang中的socket
golang中提供了一些网络编程的API,包括Dial,Listen,Accept,Read,Write,Close等。
3.1 Listen()
首先使用服务端net.Listen()方法创建套接字,绑定端口和监听端口。
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
以上是golang提供的Listen函数源码,其中network表示网络协议,如tcp,tcp4,tcp6,udp,udp4,udp6等。address为绑定的地址,返回的Listener实际上是一个套接字描述符,error中保存错误信息。
而在Linux socket中使用socket,bind和listen函数来完成同样功能。
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
3.2 Dial()
当客户端想要发起某个连接时,就会使用net.Dial()方法来发起连接。
func Dial(network, address string) (Conn, error) {
var d Dialer
return d.Dial(network, address)
}
其中network表示网络协议,address为要建立连接的地址,返回的Conn实际是标识每一个客户端的,在golang中定义了一个Conn的接口:
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
type conn struct {
fd *netFD
}
其中netFD是golang网络库里最核心的数据结构,贯穿了golang网络库所有的API,对底层的socket进行封装,屏蔽了不同操作系统的网络实现,这样通过返回的Conn,我们就可以使用golang提供的socket底层函数了。
在Linux socket中使用connect函数来创建连接:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.3 Accept()
当服务端调用net.Listen()后会开始监听指定地址,而客户端调用net.Dial()后发起连接请求,然后服务端调用net.Accept()接收请求,这里端与端的连接就建立好了,实际上到这一步也就完成了TCP中的三次握手。
Accept() (Conn, error)
golang的socket实际上是非阻塞的,但golang本身对socket做了一定处理,使其看起来是阻塞的。
在Linux socket中使用accept函数来实现同样功能:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
3.4 Write()
端与端的连接已经建立了,接下来开始进行读写操作,conn.Write()向socket写数据:
func (c *conn) Write(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
n, err := c.fd.Write(b)
if err != nil {
err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return n, err
}
其中写入的数据是一个二进制字节流,n返回的数据的长度,err保存错误信息。
Linux socket中对应的则是send函数:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
3.5 Read()
客户端发送完数据以后,服务端可以接收数据,golang中调用conn.Read()读取数据,源码如下:
func (c *conn) Read(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
n, err := c.fd.Read(b)
if err != nil && err != io.EOF {
err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return n, err
}
其参数与Write()中的含义一样,在Linux socket中使用recv函数完成此功能:
size_t recv(int sockfd, void *buf, size_t len, int flags);
3.6 Close()
当服务端或者客户端想要关闭套接字时,调用Close()方法关闭连接。
func (c *conn) Close() error {
if !c.ok() {
return syscall.EINVAL
}
err := c.fd.Close()
if err != nil {
err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return err
}
在Linux socket中使用close函数:
int close(int socketfd);
四、golang实现网络聊天程序
4.1 server.go
package main import (
"bufio"
"log"
"net"
"fmt"
) func main() {
listener, err := net.Listen("tcp", "localhost:8848")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
} type client chan <- string // an outgoing message channel var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string) // incoming messages from clients
) func broadcaster() {
clients := make(map[client]bool)
for {
select {
// broadcast incoming message to all client's outgoing message channels
case msg := <- messages:
for cli := range clients {
cli <- msg
}
case cli := <- entering:
clients[cli] = true
case cli := <- leaving:
delete(clients, cli)
close(cli)
}
}
} func handleConn(conn net.Conn) {
ch := make(chan string) // outgoing clietnt messages
go clientWriter(conn, ch) who := conn.RemoteAddr().String()
ch <- "You are " + who
messages <- who + " has arrived"
entering <- ch input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ": " + input.Text()
} leaving <- ch
messages <- who + " has left"
conn.Close()
} func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg)
}
}
server端设置了三类channel:entering、leaving以及messages用于在goroutine间共享客户端接入、离开及发送消息等数据状态。对于每一个客户端连接,server都开启单独的goroutine进行处理。对应于前述的各个channel,还设置了一个单独的broadcaster goroutine进行消息广播及客户端连接状态更新。
4.2 client.go
package main import (
"io"
"log"
"net"
"os"
) func main() {
conn, err := net.Dial("tcp", "localhost:8848")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn)
log.Println("done")
done <- struct{}{}
}()
mustCopy(conn, os.Stdin)
conn.Close()
<- done
} func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err!= nil {
log.Fatal(err)
}
}
编译两个源文件后,首先启动server程序,它监听8848端口,然后运行多个client程序。各客户端程序可以发送消息给服务端,并得到回应。每一个客户端的加入、离开以及发送的消息都会向其他在线的客户端进行广播。效果如下图所示。
Linux下Golang Socket编程原理分析与代码实现的更多相关文章
- Linux下网络socket编程——实现服务器(select)与多个客户端通信
一.关于socket通信 服务器端工作流程: 调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定 调用listen()函数监听socket() 函数创建的 ...
- LINUX 下 ipv6 socket 编程
大家都知道,随着互联网上主机数量的增多,现有的32位IP地址已经不够用了,所以推出了下一代IP地址IPv6,写网络程序的要稍微改变一下现有的网络程序适应IPv6网络是相当容易的事.对于我们来说就是IP ...
- 网络编程学习笔记:linux下的socket编程
socket是进程通信的一种方式,通过调用一些API可以实现进程间通信,建立连接以及收发信息的过程如下图所示: 这些函数的用法如下: 1.int socket(int protocolFamily, ...
- Linux下TCP/socket编程
写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文! 本博客全网唯一合法URL:ht ...
- ZT Linux系统环境下的Socket编程详细解析
Linux系统环境下的Socket编程详细解析 来自: http://blog.163.com/jiangh_1982/blog/static/121950520082881457775/ 什么是So ...
- Linux下TCP网络编程与基于Windows下C#socket编程间通信
一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使s ...
- Linux下的C编程实战
Linux下的C编程实战(一) ――开发平台搭建 1.引言 Linux操作系统在服务器领域的应用和普及已经有较长的历史,这源于它的开源特点以及其超越Windows的安全性和稳定性.而近年来, Linu ...
- 【VS开发】socket编程原理
socket编程原理 1.问题的引入 1) 普通的I/O操作过程: UNIX系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-rea ...
- Linux下C语言编程实现spwd函数
Linux下C语言编程实现spwd函数 介绍 spwd函数 功能:显示当前目录路径 实现:通过编译执行该代码,可在终端中输出当前路径 代码实现 代码链接 代码托管链接:spwd.c 所需结构体.函数. ...
随机推荐
- ECUST_Algorithm_2019_3
简要题意及解析 1001 给出一个\(n\times m\)连连看的局面,下面有\(q\)次询问:两个位置的块能否消去,即两个位置的连线是否能少于两次转折,回答\(YES/NO\).与一般的连连看不同 ...
- Pythonf反射
Python中,反射有4个方法.分别是:hasattr().getattr().setattr()和delattr(). hasattr() 定义 hasattr()函数用于判断对象是否包含对应的属性 ...
- c#处理3种json数据的方式
原文出处:http://www.jb51.net/article/48027.htm 一.C#处理简单json数据 json数据: {"result":"0", ...
- Codeforces Round #420 (Div. 2) E. Okabe and El Psy Kongroo dp+矩阵快速幂
E. Okabe and El Psy Kongroo Okabe likes to take walks but knows that spies from the Organization c ...
- mysql完美增量备份脚本
是否因为mysql太大,来回备份浪费资源带宽而发愁,如果想解决这个麻烦就需要增量备份. vi /etc/my.cnf开启日志及定期清理日志log-bin=mysql-binbinlog_format= ...
- 微信小程序中使用阿里ICON图标
由于微信小程序不支持ttf字体,只支持base64的问题,需要把从图库下载下来的字体文件中的ttf文件转码为base64后使用如图 需将图中箭头所指的字体文件通过 https://transfonte ...
- Redis 单机模式,主从模式,哨兵模式(sentinel),集群模式(cluster),第三方模式优缺点分析
Redis 的几种常见使用方式包括: 单机模式 主从模式 哨兵模式(sentinel) 集群模式(cluster) 第三方模式 单机模式 Redis 单副本,采用单个 Redis 节点部署架构,没有备 ...
- PouchContainer 容器技术演进助力阿里云原生升级
点击下载<不一样的 双11 技术:阿里巴巴经济体云原生实践> 作者 | 杨育兵(沈陵) 阿里巴巴高级技术专家 我们从 2016 年开始在集团推广全面的镜像化容器化,今年是集团全面镜像化容器 ...
- java %d %n \n
Java中,%d和%f分别用来表示输出时,替换整型输出和浮点型输出的占位符. 如: int a=28; float b = 13.0f; System.out.printf("整数是:%d% ...
- ZJUT11 多校赛补题记录
牛客第一场 (通过)Integration (https://ac.nowcoder.com/acm/contest/881/B) (未补)Euclidean Distance (https://ac ...