golang socket与Linux socket比较分析
在posix标准推出后,socket在各大主流OS平台上都得到了很好的支持。而Golang是自带runtime的跨平台编程语言,Go中提供给开发者的socket API是建立在操作系统原生socket接口之上的。但golang 中的socket接口在行为特点与操作系统原生接口有一些不同。本文将对结合一个简单的hello/hi的网络聊天程序加以分析。
一、socket简介
首先进程之间可以进行通信的前提是进程可以被唯一标识,在本地通信时可以使用PID唯一标识,而在网络中这种方法不可行,我们可以通过IP地址+协议+端口号来唯一标识一个进程,然后利用socket进行通信。
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中保存错误信息。
而在Linuxsocket中使用socket,bind和listen函数来完成同样功能
// socket(协议域,套接字类型,协议)
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
}
其中netFD是golang网络库里最核心的数据结构,贯穿了golang网络库所有的API,对底层的socket进行封装,屏蔽了不同操作系统的网络实现,这样通过返回的Conn,我们就可以使用golang提供的socket底层函数了。
在Linuxsocket中使用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做了一定处理,使其看起来是阻塞的。
在Linuxsocket中使用accept函数来实现同样功能
//sockfd是服务器套接字描述符,sockaddr返回客户端协议地址,socklen_t是协议地址长度。
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 , 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保存错误信息
Linuxsocket中对应的则是send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
3.5 Read()
客户端发送完数据以后,服务端可以接收数据,golang中调用conn.Read()读取数据,源码如下:
Read(b []byte) (n int, err error)
其参数与Write()中的含义一样,在Linuxsocket中使用recv函数完成此功能
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
3.6 Close()
当服务端或者客户端想要关闭套接字时,调用Close()方法关闭连接。
Close() error
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
}
在Linuxsocket中使用close函数
int close(int socketfd)
四、golang实现Hello/hi网络聊天程序
4.1 server.go
package main
import (
"fmt"
"net"
"strings"
)
//UserMap保存的是当前聊天室所有用户id的集合
var UserMap map[string]net.Conn = make(map[string]net.Conn)
func main() {
//监听本地所有ip的8000端口
listen_socket, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("服务启动失败")
}
//关闭监听的端口
defer listen_socket.Close()
fmt.Println("等待用户加入聊天室")
for {
//用于conn接收链接
conn, err := listen_socket.Accept()
if err != nil {
fmt.Println("连接失败")
}
//打印加入聊天室的公网IP地址
fmt.Println(conn.RemoteAddr(), "连接成功")
//定义一个goroutine,这里主要是为了并发运行
go DataProcessing(conn)
}
}
func DataProcessing(conn net.Conn) {
for {
//定义一个长度为255的切片
data := make([]byte, )
//读取客户端传来的数据,msg_length保存长度,err保存错误信息
msg_length, err := conn.Read(data)
if msg_length == || err != nil {
continue
}
//解析协议,通过分隔符"|"获取需要的数据,msg_str[0]存放操作类别
//msg_str[1]存放用户名,msg_str[2]如果有就存放发送的消息
msg_str := strings.Split(string(data[:msg_length]), "|")
switch msg_str[] {
case "nick":
fmt.Println(conn.RemoteAddr(), "的用户名是", msg_str[])
for user, message := range UserMap {
//向除自己之外的用户发送加入聊天室的消息
if user != msg_str[] {
message.Write([]byte("用户" + msg_str[] + "加入聊天室"))
}
}
//将该用户加入用户id的集合
UserMap[msg_str[]] = conn
case "send":
for user, message := range UserMap {
if user != msg_str[] {
fmt.Println("Send "+msg_str[]+" to ", user)
//向除自己之外的用户发送聊天消息
message.Write([]byte(" 用户" + msg_str[] + ": " + msg_str[]))
}
}
case "quit":
for user, message := range UserMap {
if user != msg_str[] {
//向除自己之外的用户发送退出聊天室的消失
message.Write([]byte("用户" + msg_str[] + "退出聊天室"))
}
}
fmt.Println("用户 " + msg_str[] + "退出聊天室")
//将该用户名从用户id的集合中删除
delete(UserMap, msg_str[])
}
}
}
5.2 client.go
package main
import (
"bufio"
"fmt"
"net"
"os"
)
var nick string = ""
func main() {
//拨号操作
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("连接失败")
}
defer conn.Close()
fmt.Println("连接服务成功 \n")
//创建用户名
fmt.Printf("在进入聊天室之前给自己取个名字吧:")
fmt.Scanf("%s", &nick)
fmt.Println("用户" + nick + "欢迎进入聊天室")
//向服务器发送数据
conn.Write([]byte("nick|" + nick))
//定义一个goroutine,这里主要是为了并发运行
go SendMessage(conn)
var msg string
for {
msg = ""
//由于golangz的fmt包输入字符串不能读取空格,所以此处重写了一个Scanf函数
Scanf(&msg)
if msg == "quit" {
//这里的quit,send,以及上面的nick是为了识别客户端做的是设置用户名,发消息还是退出
conn.Write([]byte("quit|" + nick))
break
}
if msg != "" {
conn.Write([]byte("send|" + nick + "|" + msg))
}
}
}
func SendMessage(conn net.Conn) {
for {
//定义一个长度为255的切片
data := make([]byte, )
//读取服务器传来的数据,msg_length保存长度,err保存错误信息
msg_length, err := conn.Read(data)
if msg_length == || err != nil {
break
}
fmt.Println(string(data[:msg_length]))
}
}
//重写的Scanf函数
func Scanf(a *string) {
reader := bufio.NewReader(os.Stdin)
data, _, _ := reader.ReadLine()
*a = string(data)
}
golang中使用goroutine实现并发
5.3 运行截图
多人聊天截图(左上角为服务端)

用户退出聊天室(左上角为服务端)

参考资料:
https://tonybai.com/2015/11/17/tcp-programming-in-golang/
https://www.jianshu.com/p/325ac02fc31c
https://blog.csdn.net/dyd961121/article/details/81252920
golang socket与Linux socket比较分析的更多相关文章
- Windows Socket和Linux Socket编程的区别 ZZ
socket相关程序从Windows移植到Linux下需要注意的: 1)头文件 Windows下winsock.h/winsock2.h Linux下sys/socket.h 错误处理:errno.h ...
- 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析
套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程. 端 ...
- Linux socket编程 DNS查询IP地址
本来是一次计算机网络的实验,但是还没有完全写好,DNS的响应请求报文的冗余信息太多了,不只有IP地址.所以这次的实验主要就是解析DNS报文.同时也需要正确的填充请求报文.如果代码有什么bug,欢迎指正 ...
- Golang网络库中socket阻塞调度源码剖析
本文分析了Golang的socket文件描述符和goroutine阻塞调度的原理.代码中大部分是Go代码,小部分是汇编代码.完整理解本文需要Go语言知识,并且用Golang写过网络程序.更重要的是,需 ...
- OpenFastPath(2):原生态Linux Socket应用如何移植到OpenFastPath上?
版本信息: ODP(Open Data Plane): 1.19.0.2 OFP(Open Fast Path): 3.0.0 1.存在的问题 OpenFastPath作为一个开源的用户态TCP/IP ...
- Linux socket 编程中存在的五个隐患
前言: Socket API 是网络应用程序开发中实际应用的标准 API.尽管该 API 简单,但是 开发新手可能会经历一些常见的问题.本文识别一些最常见的隐患并向您显示如何避免它 ...
- Linux Socket编程
“一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览 ...
- Linux Socket编程(不限Linux)【转】
转自:http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几 ...
- Linux Socket编程(不限Linux)
"一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信 ...
随机推荐
- PHP使用PhpAnalysis进行分词
1.介绍 PHPAnalysis分词程序使用居于unicode的词库,使用反向匹配模式分词,理论上兼容编码更广泛,并且对utf-8编码尤为方便. 下载地址:http://www.phpbone.com ...
- Nginx 高级配置-https 功能
Nginx 高级配置-https 功能 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HTTPS工作过程 1>.SSL/TLS SSL(Secure Socket Lay ...
- 前后端分离-Restful最佳实践
前后端分离-Restful最佳实践 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.
- 并发编程 || Java线程详解
通用线程模型 在很多研发当中,实际应用是基于一个理论再进行优化的.所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解. ...
- nfs—文件转换器
前端时间,在做一次设备升级时对nfs有了新的认识. nfs一般认为是文件共享服务器,但在实际的工作环境中,业务服务器有诸多限制,需要有加密隔离措施等等,版本升级和功能调试不同于平时的实验环境. 特别好 ...
- python 解决粘包问题的例子(ftp文件的上传与下载)简单版本
服务端 ! /user/bin/env python3 -- coding:utf_8 -- """ Author:Markli # 2019/9/9,16:41 &qu ...
- 补充:垃圾回收机制、线程池和ORM缺点
补充:垃圾回收机制.线程池和ORM缺点 垃圾回收机制不仅有引用计数,还有标记清除和分代回收 引用计数就是内存地址的门牌号,为0时就会回收掉,但是会出现循环引用问题,这种情况下会导致内存泄漏(即不会被用 ...
- POJ2239-Selecting Courses-(匈牙利算法)
题意:n门课,每门各自有t个开课时间,在不冲突的情况下选最多课. 题解:把周p第q节课转化为数值sum,表示在一周7*12节课中排第几节,用二分图最大匹配. #include<stdio.h&g ...
- thinkphp5.0 中简单处理微信支付异步通知
public function wx_notify(){ $postStr = $GLOBALS["HTTP_RAW_POST_DATA"]; libxml_disable_ent ...
- Win10解决修改host没有权限问题(其他文件同理) 一步都不能少哦:先添加再授权
Step1:右键文件选择属性,选择安全,点击编辑: Step2:在弹窗中点击添加,在弹窗中点击高级: Step3:在弹窗中点击立即查找,选中当前用户,点击确定: Step4:此时选中用户已经被加入进来 ...