Go的Tcp服务器应该设置多少个beginaccepttcpclient合适

12220人阅读
技术(21)
需要import &net&包IP类型,其中一个重要的方法是IP.ParseIP(ipaddr string)来判断是否是合法的IP地址
TCP Client
func (c *TCPConn) Write(b []byte) (n int, err os.Error)用于发送数据,返回发送的数据长度或者返回错误,是TCPConn的方法func (c *TCPConn) Read(b []byte) (n int, err os.Error)用于接收数据,返回接收的长度或者返回错误,是TCPConn的方法
TCPAddr类型,保存TCP的地址信息,包括地址和端口
type TCPAddr struct {
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)获取一个TCPAddr,参数都是string类型,net是个const string,包括tcp4,tcp6,tcp一般使用tcp,兼容v4和v6,addr表示ip地址,包括端口号,如:80之类的func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)用来连接(connect)到远程服务器上,net表示协议方式,tcp,tcp4或者tcp6,laddr表示本机地址,一般为nil,raddr表示远程地址,这里的laddr和raddr都是TCPAddr类型的,一般是上一个函数的返回值。
作为一个TCP的客户端,基本的操作流程如下:
service=&:80&
tcpAddr, err := net.ResolveTCPAddr(&tcp4&, service)
conn, err := net.DialTCP(&tcp&, nil, tcpAddr)
_, err = conn.Write([]byte(&HEAD / HTTP/1.0\r\n\r\n&))
_, err = conn.Read(b) / result, err := ioutil.ReadAll(conn)
TCP Server
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)用来监听端口,net表示协议类型,laddr表示本机地址,是TCPAddr类型,注意,此处的laddr包括端口,返回一个*TCPListener类型或者错误func (l *TCPListener) Accept() (c Conn, err os.Error)用来返回一个新的连接,进行后续操作,这是TCPListener的方法,一般TCPListener从上一个函数返回得来。
服务器的基本操作流程为:
service:=&:9090&
tcpAddr, err := net.ResolveTCPAddr(&tcp4&, service)
l,err := net.ListenTCP(&tcp&,tcpAddr)
conn,err := l.Accept()
go Handler(conn) //此处使用go关键字新建线程处理连接,实现并发
实现一个公共聊天服务器。
服务器接收客户端的信息接收完以后将客户端的信息发送到所有的客户端上客户端使用/quit退出聊天只使用一套代码,通过命令行参数启动服务器还是客户端
package main
////////////////////////////////////////////////////////
//错误检查
////////////////////////////////////////////////////////
func checkError(err error,info string) (res bool) {
if(err != nil){
fmt.Println(info+&
& + err.Error())
return false
return true
////////////////////////////////////////////////////////
//服务器端接收数据线程
数据连接 conn
通讯通道 messages
////////////////////////////////////////////////////////
func Handler(conn net.Conn,messages chan string){
fmt.Println(&connection is connected from ...&,conn.RemoteAddr().String())
buf := make([]byte,1024)
lenght, err := conn.Read(buf)
if(checkError(err,&Connection&)==false){
conn.Close()
if lenght & 0{
buf[lenght]=0
//fmt.Println(&Rec[&,conn.RemoteAddr().String(),&] Say :& ,string(buf[0:lenght]))
reciveStr :=string(buf[0:lenght])
messages &- reciveStr
////////////////////////////////////////////////////////
//服务器发送数据的线程
连接字典 conns
数据通道 messages
////////////////////////////////////////////////////////
func echoHandler(conns *map[string]net.Conn,messages chan string){
msg:= &- messages
fmt.Println(msg)
for key,value := range *conns {
fmt.Println(&connection is connected from ...&,key)
_,err :=value.Write([]byte(msg))
if(err != nil){
fmt.Println(err.Error())
delete(*conns,key)
////////////////////////////////////////////////////////
//启动服务器
// 端口 port
////////////////////////////////////////////////////////
func StartServer(port string){
service:=&:&+port //strconv.Itoa(port);
tcpAddr, err := net.ResolveTCPAddr(&tcp4&, service)
checkError(err,&ResolveTCPAddr&)
l,err := net.ListenTCP(&tcp&,tcpAddr)
checkError(err,&ListenTCP&)
conns:=make(map[string]net.Conn)
messages := make(chan string,10)
//启动服务器广播线程
go echoHandler(&conns,messages)
fmt.Println(&Listening ...&)
conn,err := l.Accept()
checkError(err,&Accept&)
fmt.Println(&Accepting ...&)
conns[conn.RemoteAddr().String()]=conn
//启动一个新线程
go Handler(conn,messages)
////////////////////////////////////////////////////////
//客户端发送线程
发送连接 conn
////////////////////////////////////////////////////////
func chatSend(conn net.Conn){
var input string
username := conn.LocalAddr().String()
fmt.Scanln(&input)
if input == &/quit&{
fmt.Println(&ByeBye..&)
conn.Close()
os.Exit(0);
lens,err :=conn.Write([]byte(username + & Say :::& + input))
fmt.Println(lens)
if(err != nil){
fmt.Println(err.Error())
conn.Close()
////////////////////////////////////////////////////////
//客户端启动函数
远程ip地址和端口 tcpaddr
////////////////////////////////////////////////////////
func StartClient(tcpaddr string){
tcpAddr, err := net.ResolveTCPAddr(&tcp4&, tcpaddr)
checkError(err,&ResolveTCPAddr&)
conn, err := net.DialTCP(&tcp&, nil, tcpAddr)
checkError(err,&DialTCP&)
//启动客户端发送线程
go chatSend(conn)
//开始客户端轮训
buf := make([]byte,1024)
lenght, err := conn.Read(buf)
if(checkError(err,&Connection&)==false){
conn.Close()
fmt.Println(&Server is dead ...ByeBye&)
os.Exit(0)
fmt.Println(string(buf[0:lenght]))
////////////////////////////////////////////////////////
//参数说明:
// 启动服务器端:
Chat server [port]
eg: Chat server 9090
// 启动客户端:
Chat client [Server Ip Addr]:[Server Port]
eg: Chat client 192.168.0.74:9090
////////////////////////////////////////////////////////
func main(){
if len(os.Args)!=3
fmt.Println(&Wrong pare&)
os.Exit(0)
if os.Args[1]==&server& && len(os.Args)==3 {
StartServer(os.Args[2])
if os.Args[1]==&client& && len(os.Args)==3 {
StartClient(os.Args[2])
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:275055次
积分:3087
积分:3087
排名:第8382名
原创:53篇
评论:170条
欢迎大家关注我的微信号:XJJ267 【西加加语言】,或者扫描下面二维码哦
不是每天都推送,隔几天推一次,关注技术和生活
一个已经过了而立之年的码农。
成长在湘江之畔
求学于麓山脚下
工作在帝都北京
之前一直做嵌入式方向的开发,后来慢慢转向了管理,中间有很长时间没有写过代码了,但是自己还是非常热爱代码的工作,后来,终于下决心转行到互联网做一个码农。
一个机缘巧合的机会,在一家成立了很长时间的电商网站负责整个网站的搜索和排序系统,让我过了一个非常精彩的2014年,而现在,我又有新的征程了。
C/C++,go,Python,Obj-c是我的菜 hadoop和strome也还行偶尔也吃一些erlang,haskell。
算法也能聊聊,机器学习也能聊聊,搜索引擎也能聊聊,推荐系统也能聊聊,mips和arm也能聊聊,驱动程序也能聊聊,FPGA也能聊聊,呵呵,但都仅仅是聊聊啊。。。
平时就是看书,跑步,画画
GitHub:/wyh267
欢迎交朋友:
(3)(1)(2)(3)(8)(2)(14)(4)(7)(1)(2)(1)(1)(6)Sponsered by
15.1 tcp服务器
这部分我们将使用TCP协议和在14章讲到的协程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个协程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。
服务器代码,单独的一个文件:
package main
func main() {
fmt.Println("Starting the server ...")
// 创建 listener
listener, err := net.Listen("tcp", "localhost:50000")
if err != nil {
fmt.Println("Error listening", err.Error())
return //终止程序
// 监听并接受来自客户端的连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting", err.Error())
return // 终止程序
go doServerStuff(conn)
func doServerStuff(conn net.Conn) {
buf := make([]byte, 512)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading", err.Error())
return //终止程序
fmt.Printf("Received data: %v", string(buf))
我们在main()创建了一个net.Listener的变量,他是一个服务器的基本函数:用来监听和接收来自客户端的请求(来自localhost即IP地址为127.0.0.1端口为50000基于TCP协议)。这个Listen()函数可以返回一个error类型的错误变量。用一个无限for循环的listener.Accept()来等待客户端的请求。客户端的请求将产生一个net.Conn类型的连接变量。然后一个独立的携程使用这个连接执行doServerStuff(),开始使用一个512字节的缓冲data来读取客户端发送来的数据并且把它们打印到服务器的终端;当客户端发送的所有数据都被读取完成时,携程就结束了。这段程序会为每一个客户端连接创建一个独立的携程。必须先运行服务器代码,再运行客户端代码。
客户端代码写在另外一个文件client.go中:
package main
func main() {
//打开连接:
conn, err := net.Dial("tcp", "localhost:50000")
if err != nil {
//由于目标计算机积极拒绝而无法创建连接
fmt.Println("Error dialing", err.Error())
return // 终止程序
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("First, what is your name?")
clientName, _ := inputReader.ReadString('\n')
// fmt.Printf("CLIENTNAME %s", clientName)
trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n"
// 给服务器发送信息直到程序退出:
fmt.Println("What to send to the server? Type Q to quit.")
input, _ := inputReader.ReadString('\n')
trimmedInput := strings.Trim(input, "\r\n")
// fmt.Printf("input:--s%--", input)
// fmt.Printf("trimmedInput:--s%--", trimmedInput)
if trimmedInput == "Q" {
_, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput))
客户端通过net.Dial创建了一个和服务器之间的连接
它通过无限循环中的os.Stdin接收来自键盘的输入直到输入了“Q”。注意使用\r和\n换行符分割字符串(在windows平台下使用\r\n)。接下来分割后的输入通过connection的Write方法被发送到服务器。
当然,服务器必须先启动好,如果服务器并未开始监听,客户端是无法成功连接的。
如果在服务器没有开始监听的情况下运行客户端程序,客户端会停止并打印出以下错误信息:对tcp 127.0.0.1:50000发起连接时产生错误:由于目标计算机的积极拒绝而无法创建连接。
打开控制台并转到服务器和客户端可执行程序所在的目录,Windows系统下输入server.exe(或者只输入server),Linux系统下输入./server。
接下来控制台出现以下信息:Starting the server ...
在Windows系统中,我们可以通过CTRL/C停止程序。
然后开启2个或者3个独立的控制台窗口,然后分别输入client回车启动客户端程序
以下是服务器的输出(在移除掉512字节的字符串中内容为空的区域后):
Starting the Server ...
Received data: IVO says: Hi Server, what's up ?
Received data: CHRIS says: Are you busy server ?
Received data: MARC says: Don't forget our appointment tomorrow !
当客户端输入 Q 并结束程序时,服务器会输出以下信息:
Error reading WSARecv tcp 127.0.0.1:50000: The specified network name is no longer available.
在网络编程中net.Dial函数是非常重要的,一旦你连接到远程系统,就会返回一个Conn类型接口,我们可以用它发送和接收数据。Dial函数巧妙的抽象了网络结构及传输。所以IPv4或者IPv6,TCP或者UDP都可以使用这个公用接口。
下边这个示例先使用TCP协议连接远程80端口,然后使用UDP协议连接,最后使用TCP协议连接IPv6类型的地址:
// make a connection with www.example.org:
package main
func main() {
conn, err := net.Dial("tcp", "192.0.32.10:80") // tcp ipv4
checkConnection(conn, err)
conn, err = net.Dial("udp", "192.0.32.10:80") // udp
checkConnection(conn, err)
conn, err = net.Dial("tcp", "[d0:200::10]:80") // tcp ipv6
checkConnection(conn, err)
func checkConnection(conn net.Conn, err error) {
if err != nil {
fmt.Printf("error %v connecting!")
os.Exit(1)
fmt.Println("Connection is made with %v", conn)
下边也是一个使用net包从socket中打开,写入,读取数据的例子:
package main
func main() {
= "www.apache.org"
= host + ":" + port
string = "GET / \n"
= make([]uint8, 4096)
// 创建一个socket
con, err := net.Dial("tcp", remote)
// 发送我们的消息,一个http GET请求
io.WriteString(con, msg)
// 读取服务器的响应
for read {
count, err = con.Read(data)
read = (err == nil)
fmt.Printf(string(data[0:count]))
con.Close()
练习 15.1 编写新版本的客户端和服务器( / ):
增加一个检查错误的函数checkError(error);讨论如下方案的利弊:为什么这个重构可能并没有那么理想?看看在中它是如何被解决的
使客户端可以通过发送一条命令SH来关闭服务器
让服务器可以保存已经连接的客户端列表(他们的名字);当客户端发送WHO指令的时候,服务器将显示如下列表:
This is the client list: 1:active, 0=inactive
User IVO is 1
User MARC is 1
User CHRIS is 1
注意:当服务器运行的时候,你无法编译/连接同一个目录下的源码来产生一个新的版本,因为server.exe正在被操作系统使用而无法被替换成新的版本。
下边这个版本的 simple_tcp_server.go 从很多方面优化了第一个tcp服务器的示例 server.go 并且拥有更好的结构,它只用了80行代码!
示例 15.5 :
// Simple multi-thread/multi-core TCP server.
package main
const maxRead = 25
func main() {
flag.Parse()
if flag.NArg() != 2 {
panic("usage: host port")
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
listener := initServer(hostAndPort)
conn, err := listener.Accept()
checkError(err, "Accept: ")
go connectionHandler(conn)
func initServer(hostAndPort string) *net.TCPListener {
serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort)
checkError(err, "Resolving address:port failed: '"+hostAndPort+"'")
listener, err := net.ListenTCP("tcp", serverAddr)
checkError(err, "ListenTCP: ")
println("Listening to: ", listener.Addr().String())
return listener
func connectionHandler(conn net.Conn) {
connFrom := conn.RemoteAddr().String()
println("Connection from: ", connFrom)
sayHello(conn)
var ibuf []byte = make([]byte, maxRead+1)
length, err := conn.Read(ibuf[0:maxRead])
ibuf[maxRead] = 0 // to prevent overflow
switch err {
handleMsg(length, err, ibuf)
case os.EAGAIN: // try again
goto DISCONNECT
DISCONNECT:
err := conn.Close()
println("Closed connection: ", connFrom)
checkError(err, "Close: ")
func sayHello(to net.Conn) {
obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'}
wrote, err := to.Write(obuf)
checkError(err, "Write: wrote "+string(wrote)+" bytes.")
func handleMsg(length int, err error, msg []byte) {
if length & 0 {
print("&", length, ":")
for i := 0; ; i++ {
if msg[i] == 0 {
fmt.Printf("%c", msg[i])
print("&")
func checkError(error error, info string) {
if error != nil {
panic("ERROR: " + info + " " + error.Error()) // terminate program
(译者注:应该是由于go版本的更新,会提示os.EAGAIN undefined ,修改后的代码:)
都有哪些改进?
服务器地址和端口不再是硬编码,而是通过命令行传入参数并通过flag包来读取这些参数。这里使用了flag.NArg()检查是否按照期望传入了2个参数:
if flag.NArg() != 2{
panic("usage: host port")
传入的参数通过fmt.Sprintf函数格式化成字符串
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
在initServer函数中通过net.ResolveTCPAddr指定了服务器地址和端口,这个函数最终返回了一个*net.TCPListener
每一个连接都会以携程的方式运行connectionHandler函数。这些开始于当通过conn.RemoteAddr()获取到客户端的地址
它使用conn.Write发送改进的go-message给客户端
它使用一个25字节的缓冲读取客户端发送的数据并一一打印出来。如果读取的过程中出现错误,代码会进入switch语句的default分支关闭连接。如果是操作系统的EAGAIN错误,它会重试。
所有的错误检查都被重构在独立的函数'checkError'中,用来分发出现的上下文错误。
在命令行中输入simple_tcp_server localhost 50000来启动服务器程序,然后在独立的命令行窗口启动一些client.go的客户端。当有两个客户端连接的情况下服务器的典型输出如下,这里我们可以看到每个客户端都有自己的地址:
E:\Go\GoBoek\code examples\chapter 14&simple_tcp_server localhost 50000
Listening to: 127.0.0.1:50000
Connection from: 127.0.0.1:49346
&25:Ivo says: Hi server, do y&&12:ou hear me ?&
Connection from: 127.0.0.1:49347
&25:Marc says: Do you remembe&&25:r our first meeting serve&&2:r?&
net.Error:
这个net包返回错误的错误类型,下边是约定的写法,不过net.Error接口还定义了一些其他的错误实现,有些额外的方法。
package net
type Error interface{
Timeout() bool // 错误是否超时
Temporary() bool // 是否是临时错误
通过类型断言,客户端代码可以用来测试net.Error,从而区分哪些临时发生的错误或者必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。
// in a loop - some function returns an error err
if nerr, ok := err.(net.Error); ok && nerr.Temporary(){
time.Sleep(1e9)
continue // try again
if err != nil{
log.Fatal(err)MarioTCP:一个可单机支持千万并发连接的TCP服务器
注:如果用此服务器做变长data的传输,请在业务处理函数中为input buffer增加清空功能(一行memset搞定;也可以在mariotcp核心代码mario_network.c的read功能中增加,mariotcp为了追求性能极限没做此设置)。
注:很多同学通过代码中的qq号加了我,qq好友经常爆满;而沟通的问题都一样,所以不再qq接受好友请求;请大家还是留言到评论中吧,尽快回复!最近工作主要是写代码,落个清闲,所以准备优化一下MarioTCP,1.0版本敬请关注!
MrioTCP,超级马里奥,顾名思义,他不仅高效,而且超级简易和好玩。同时他可以是一个很简洁的Linux C 开发学习工程。毫不夸张的说,如果全部掌握这一个工程,你会成为一个Linux C的牛人;当然,你也可以通过源码包的mario.c(maritcp服务器示例程序)来学习,可以很快入门上手进行Linux C开发。
经过两个多月的测试(编写c++客户端测试及调优系统参数),测试结果得到单机最大带宽吞吐1000M,测试最高TCP长连接100万,每秒处理连接数达4万,此时系统压力load值很低。总之,它可以发挥一台服务器的最大极限以提供最高性能的服务;而且经过完备测试,运行稳定且占用系统资源非常少。
他是建立在Sourceforge上的一个开源项目,由源码的作者冯建华(JohnFong)发起。源码可以在Sourceforge上下载。
sourceforge下载:
csdn下载:
51cto下载:
Getting Started
用MarioTCP来建立一个性能强大的TCP服务器非常简易!   工程源码包就是一个非常简洁的例子,生成了一个tcp服务器程序:maritcp。
  源码包中:
  mario.c是简易例子的main程序,直接make可以编译出maritcp,一个tcp服务器,业务逻辑只有一个功能:统计同时在线socket数、每隔1分钟输出一次。
  mario文件夹,MarioTCP的核心代码,make可以直接编译出静态的libmario.a。MarioTCP核心架构后续会介绍。
  test文件夹,是一个稍显简陋的客户端测试程序,通过与服务器建立连接、发送LOGIN包登陆服务器,此时maritcp服务器会使同时在线加1,客户端断开时服务器在线数减1。
现在讲一下如何定制一个自己业务逻辑的tcp服务器,只需五步:
  1、初始化SERVER
  SERVER *server = init_server(conf-&port, conf-&workernum, conf-&connnum, conf-&timeout, conf-&timeout);
  传入参数分别是:
  服务器Listen端口,工作线程数,每个线程支持的连接数,读超时时间,写超时时间。
  workernum * connum 就是服务器支持的长连接数,一个worker可以轻松支持10万长连接。
2、实现业务逻辑函数并注册
  具体业务逻辑函数请见Function模块。可通过mario.h中定义的名为“regist_*”的函数来注册。
  *& 注册业务处理函数
  void regist_akg_func(uint16 id, FUNC_PTR func);
  id可以是0-65535的任意数,此id封装在MarioTCP的协议中(见本文最后)。
  id的范围,可以根据业务逻辑来定制,例如maritcp通过protocol.h中定义的CMD结构体来设定:
  typedef enum _CMD {
  CMD_FUNCTION_BASE = 0x6100,
  CMD_FUNCTION_LOGIN = 0x6101
  } CMD;
如果你想为maritcp增加一个&say_hello&的服务,可以这么做:
  1)在CMD中增加:CMD_FUNCTION_SAY_HELLO = 0x602
  2) 在function中增加函数:
  sint32 say_hello(CONN c) {
  I)通过CONN来解析客户端发过来请求的参数
  II)将“hello”设定到c-&out_buf
  III)bufferevent_write(c-&bufev, c-&out_buf, hrsp-&pkglen);
  IV)return 0;
  3)在mario.c中增加:regist_akg_func(CMD_FUNCTION_SAY_HELLO, say_hello);
怎么样?自己定制业务逻辑,还是很简单高效吧!
3、启动日志线程start_log_thread()
  MarioTCP的日志功能封装还不够好,在“go on 1.0.0”页面中继续讨论...
4、启动服务器start_server((void*) server);
  OK,一个可以支持100万甚至更多长连接的TCP服务器,诞生了!
Go On 1.0.0
第一个发布版本为0.9.9,尽管用这个包,通过几分钟就可以实现一个定制了你的业务逻辑的、稳定高效的TCP服务器,但是MarioTCP还有很多有待完善的地方,让我们一起尽快解决如下问题,让MarioTCP-1.0.0尽快发布!
1、MarioTCP协议如何优化
  为了使MarioTCP足够安全,规定了一个简易的MarioTCP协议,经过三次握手连接到MarioTCP的client,接下来发的包要求格式必须是“HEAD+Data”的形式,而HEAD结构体定义在mario_akg.h中:
  typedef struct _HEAD {
  uint32
  uint16
  uint16 akg_
  } HEAD;
  pkglen是整个包的长度,即“HEAD+Data”。
  akg_id及自定义的业务逻辑函数对应的id,例如“Getting Started”页面中的CMD_FUNCTION_SAY_HELLO
  stx是你自定义的协议密文,通过regist_stx(uint32 stx)来注册(见mario.c)
尽管MarioTCP的协议足够简单了,而且协议最开头的密文可以自定义,但是是否可以更简单或者无协议,以最大程度的方便开发使用,需要大家的建议和帮助!
2、日志系统过于死板
  MarioTCP有一套自成系统的日志功能,但是比较晦涩难懂。
  接下来再展开...
3、业务逻辑稳定性支持
  MarioTCP对于网络连接和读写,非常高效和稳定。
  但是MarioTCP的线程池是固定个数的,且是全局唯一初始化的,死掉的线程不可再重启;分配网络任务的Master线程不具备监听worker的功能,一个线程死掉了、任务却还会一直分配过来,造成服务堆积且不处理。如果业务逻辑如果非常复杂和低效,就会出现这个问题。
  在大型线上项目中,用到MarioTCP的地方,都会通过业务逻辑模块的监听、告警及程序自动处理来避免上述问题。由于时间问题还没有把此功能抽象到MarioTCP中。
  这件事情,近期我会抓紧处理。也希望有朋友建议和帮助!!
Why Supper
一、为什么超级高效
  1、网络服务用到的所有结构体和内存都是启动程序时初始化的,无销毁,无回收。
  无销毁好理解,不解释。
  无回收,是指所有内存单元拿来即用,用完及可,不用做reset操作。
  2、一个master线程进行accept
  经过测试发现多进程或线程进行accept和一个进程或线程accept,在极限压力下区别不大。
  一个master比多个master好在不用再通过锁来解决同步问题。
  3、master与worker时单一生产者消费者模式,完全无锁通信
  不光accept无锁,分配connection、后续的conncetion处理都是无锁的。
  甚至业务逻辑(见示例maritcp的统计在线数功能)、MarioTCP的日志系统(这也是日志系统抽象不够的一个原因,之前的设计太依赖于整体架构了)都是无锁处理的!
  4、一个worker一套libevent环境
  libevent处理10万长连接的网络读写事件,其性能达到最大化了。
  每个worker都独立一套libevent,这个结构经过测试,发现开销很小、性能很高。
二、单机百万长连接、四万cps(连接每秒)如何做测试得来
  1、设置系统最大文件数为unlimited
  2、设置系统的tcp内存内核参数到256M以上
  3、设置系统的ip到15个,那么可服务的长连接数理论上最少15*()个
  4、用epoll或libevent开一个可同时连接5w的客户端程序;程序还要实现每秒随机挑选1000个连接断掉,并再新创建1000个连接。另外在随机挑选几千连接发包。
  同时再多台机器上开启20个客户端,那么就是100w长连接,每秒2w个连接断掉、2w个新连接加入进来,并且有若干包发过来。
  5、设置服务端可重用SYN_WAIT的连接;客户端断连接的方式是主动断掉(防止客户端程序端口堆积)
  总之很折腾的一个测试,前前后后大约2个多月才测试完毕。
  以上内容凭记忆写的,怕有错误或疏漏,回头为了公布测试代码和测试结果给大家,会再次开发、测试并调整补过上述内容。
版权声明:本文为博主原创文章,未经博主允许不得转载。
这就是深入浅出,言简意赅的代码描述了。
学习下!很实用的东东!
顶一个!!
好文章 不得不顶
我以老程序员的身份怒赞一下!
刚下来看了一下代码,思想还是很不错的,master线程负责accpet连接,然后用wirte的方式去通知worker线程,这个方式跟memcached所用的一样。我这里提一个我自己的实现方法,我是每个worker线程都有一个新连接数组,假如是1024吧,然后master线程accept完连接并创建好对象之后就把指针放到对应的worker线程新连接数组里面,然后worker现在都是有自己的独立的poll的,跟你的一样,然后worker线程每次轮询完之后就去看自己的新连接数组是否有新的连接,有就取出来插入自己的poll里面。整个过程也是无锁的,而且连wirte系统调用都省了。
everlastinging
[reply]maijian[/reply]呵呵,不错;不过worker不用轮询,可以通过事件的方式处理;MarioTCP中已经实现了:)
[reply]everlastinging[/reply]楼主,代码在centos 6.4编译了发现缺少event,h文件,
[reply]maijian[/reply]同问,服务器不是用一个IP,一个端口就可以了吗?
不错,不错·
好东西,收了
everlastinging
我本人也只交流技术:)
一直使用muduo
BadMan2007
要点脸吧。。。。
设置系统的ip到15个,那么可服务的长连接数理论上最少15*()个这个地方看起来像是服务器的设置,为什么要把IP设成15个? 可服务的长连接个数和这个有关系吗?
everlastinging
[reply]wary2012[/reply]是服务器的设置,一个ip对应的端口是65535个,低1024个系统保留所以为了支持百万连接,必须扩展ip
[reply]everlastinging[/reply]svr不是占用一个listen端口么~~
可否借鉴一下netty
everlastinging
[reply]yang[/reply]nio和mina的源码都看过,比libevent少了一层;一直无缘好好学习一下netty,最近研究研究...
过来学习下~~~多学习学习~~~[url=/]太原新能源[/url]
chenyoufu123
我最想知道他的超时机制是怎么样的, 如果来了ddos攻击能不能hold住, 还有对tcp 乱序和重传的处理机制。不知道这个百万级有没有考虑这些网络中的正常情况。
everlastinging
[reply]chenyoufu123[/reply]网络安全这方面经验少,基本靠路由器和防火墙了...tcp的乱序和重传,设过几个参数而已,不知兄台搞过tcp协议层的开发吧,请给多讲讲:)本系统是架构在epoll之上的。
代码在学习中,不知道你们在编译的时候有没有问题,我这边编译的时候编译不过,缺少event,h文件,这个在网上下载了,还是编译有问题!求解!
xladykiller
./mario_conn.h:22:19: error: event.h: No such file or directoryIn file included from ./mario.h:22,
from ./mario.c:12:请问直接编译 能通过吗?centos6 我直接输入make提示,找不到event.h这个文件。
[reply]xladykiller[/reply]我的和你一样的!不知道怎么办?求楼主指点!
[reply]jsh13417[/reply]去下载个libevent库!~
everlastinging
[reply]jsh13417[/reply]对头!
everlastinging
[reply]xladykiller[/reply]libevent用默认的configure、make、make install即可!
dragonandlion
这个根本没有新意
可以看下我的博文: http://blog.csdn.net/kyee/article/details/RC3.0(跨平台, linux/windows互通)下载地址: http://download.csdn.net/detail/kyee/5168976
本来想看看牛逼的东西, 但是一看...
对需要进阶网络编程的人来说价值很好
从代码来看, 这个服务器就是用libevent写的一个很平常的server, 何来高效之说. 作者相关经验还有很多不足之处, 例如没有考虑内存使用问题, 没有考虑分配内存一般会清零问题, 没有考虑对参数进行检查问题, 没有考虑错误日志等等..还需要进一步的提高. 作者应该再继续深入研究一些东西, 而不要急于表现. 多看看UNP.
everlastinging
[reply]huahuaua[/reply]&例如没有考虑内存使用问题, 没有考虑分配内存一般会清零问题, 没有考虑对参数进行检查问题, 没有考虑错误日志等等&请考虑一下,代码中为什么没这么做,呵呵;请仔细多读读,这个 代码谈不上牛逼,但确实还是有些精巧的地方的,你提的这四方面里面都考虑到了。UNP我是得再好好学学,多谢:)
学习学习!!
好多错误啊,这些是我精简了的,mario/mario_conn.h:22:19: 错误:event.h:没有那个文件或目录In file included from mario/mario.h:22,
from mario.c:15:mario/mario_network.h:44: 错误:字段 ‘notify_event’ 的类型不完全mario/mario_network.h:61: 错误:字段 ‘listen_event’ 的类型不完全在包含自 mario/mario.h:21 的文件中,
从 config.h:4,
从 config.c:5:mario/mario_conn.h:22:19: 错误:event.h:没有那个文件或目录mario/mario_network.h:61: 错误:字段 ‘listen_event’ 的类型不完全在包含自 mario/mario.h:21 的文件中,
从 function.h:14,
从 function.c:10:mario/mario_conn.h:22:19: 错误:event.h:没有那个文件或目录In file included from mario/mario.h:22,
from function.h:14,
from function.c:10:mario/mario_network.h:44: 错误:字段 ‘notify_event’ 的类型不完全mario/mario_network.h:61: 错误:字段 ‘listen_event’ 的类型不完全在包含自 mario/mario.h:21 的文件中,
从 collect.c:11:mario/mario_conn.h:22:19: 错误:event.h:没有那个文件或目录make: *** [maritcp] 错误 1
everlastinging
[reply]craigtao[/reply]编译的问题
csdn_zhang99
mark, 留着学习用
本分类共有文章3篇,更多信息详见
& 2012 - 2016 &
&All Rights Reserved. &
/*爱悠闲图+*/
var cpro_id = "u1888441";}

我要回帖

更多关于 tcp listen accept 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信