socket通信 smallchat简介

文章目录

  • 前言
  • 一、socket的基本操作
    • (1) socket()函数
    • (2) bind()函数
    • (3) listen()、connect()函数
    • (4) accept()函数
    • (5) read()、write()等函数
    • (6) close()函数
  • 二、smallchat
    • 代码流程
    • smallchat-server.c
    • smallchat-client.c
    • chatlib.c
  • 参考资料

前言

本文介绍了socket通信的相关API,以及Redis 创始人 antirez 用纯 C 语言代码写了一个聊天服务器的最小编程示例,Smallchat。

一、socket的基本操作

socket是“open—write/read—close”模式的一种实现,下面以TCP为例,介绍几个基本的socket接口函数。

(1) socket()函数

int socket(int family, int type, int protocol);

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

  • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
  • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

(2) bind()函数

正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

函数的三个参数分别为:

  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ 
}; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */
}; 
  • addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

网络字节序与主机字节序:

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

  • Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  • Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

(3) listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

(4) accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

(5) read()、write()等函数

万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);#include <sys/types.h>
#include <sys/socket.h>     
ssize_t recv(int sockfd, void *buf, size_t len, int flags);        
ssize_t send(int sockfd, const void *buf, size_t len, int flags);       
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);  
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);       

read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。
1)write的返回值大于0,表示写了部分或者是全部的数据。
2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

(6) close()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

#include <unistd.h>
int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

二、smallchat

Smallchat 源代码已托管至 GitHub:https://github.com/antirez/smal
在这里插入图片描述
antirez 表示,编写这个示例是为了帮助他的前端开发朋友了解系统编程知识,比如单个进程执行多路复用、获取客户端状态,并在客户端拥有新数据后尝试快速访问此类状态,等等。

代码流程

1.initChat初始化全局变量,同时创建服务端监听fd保存到全局变量Chat->serversock中
2.开始while死循环
3.先初始化fd_set集合
4.将监听fd和客户端fd放入到fd_set集合中
5.调用系统函数select对fd_set集合进行事件监测,同时将监测到结果保存到fd_set中
6.最后在分别对监听fd和客户端fd在结果fd_set中是否有事件进行判断
7.分别进行对应的业务处理

smallchat-server.c

/* smallchat.c -- Read clients input, send to all the other connected clients.** Copyright (c) 2023, Salvatore Sanfilippo <antirez at gmail dot com>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions are met:**   * Redistributions of source code must retain the above copyright notice,*     this list of conditions and the following disclaimer.*   * Redistributions in binary form must reproduce the above copyright*     notice, this list of conditions and the following disclaimer in the*     documentation and/or other materials provided with the distribution.*   * Neither the project name of nor the names of its contributors may be used*     to endorse or promote products derived from this software without*     specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE* POSSIBILITY OF SUCH DAMAGE.*/#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
#include <unistd.h>#include "chatlib.h"/* ============================ Data structures =================================* The minimal stuff we can afford to have. This example must be simple* even for people that don't know a lot of C.* =========================================================================== */#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.
#define SERVER_PORT 7711/* This structure represents a connected client. There is very little* info about it: the socket descriptor and the nick name, if set, otherwise* the first byte of the nickname is set to 0 if not set.* The client can set its nickname with /nick <nickname> command. */
struct client {int fd;     // Client socket.char *nick; // Nickname of the client.
};/* This global structure encapsulates the global state of the chat. */
struct chatState {int serversock;     // Listening server socket.int numclients;     // Number of connected clients right now.int maxclient;      // The greatest 'clients' slot populated.struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding// slot of their socket descriptor.
};struct chatState *Chat; // Initialized at startup./* ====================== Small chat core implementation ========================* Here the idea is very simple: we accept new connections, read what clients* write us and fan-out (that is, send-to-all) the message to everybody* with the exception of the sender. And that is, of course, the most* simple chat system ever possible.* =========================================================================== *//* Create a new client bound to 'fd'. This is called when a new client* connects. As a side effect updates the global Chat state. */
struct client *createClient(int fd) {char nick[32]; // Used to create an initial nick for the user.int nicklen = snprintf(nick,sizeof(nick),"user:%d",fd);struct client *c = chatMalloc(sizeof(*c));socketSetNonBlockNoDelay(fd); // Pretend this will not fail.c->fd = fd;c->nick = chatMalloc(nicklen+1);memcpy(c->nick,nick,nicklen);assert(Chat->clients[c->fd] == NULL); // This should be available.Chat->clients[c->fd] = c;/* We need to update the max client set if needed. */if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;Chat->numclients++;return c;
}/* Free a client, associated resources, and unbind it from the global* state in Chat. */
void freeClient(struct client *c) {free(c->nick);close(c->fd);Chat->clients[c->fd] = NULL;Chat->numclients--;if (Chat->maxclient == c->fd) {/* Ooops, this was the max client set. Let's find what is* the new highest slot used. */int j;for (j = Chat->maxclient-1; j >= 0; j--) {if (Chat->clients[j] != NULL) {Chat->maxclient = j;break;}}if (j == -1) Chat->maxclient = -1; // We no longer have clients.}free(c);
}/* Allocate and init the global stuff. */
void initChat(void) {Chat = chatMalloc(sizeof(*Chat)); // 封装了一层malloc,申请内存失败时直接退出程序了memset(Chat,0,sizeof(*Chat));/* No clients at startup, of course. */Chat->maxclient = -1;Chat->numclients = 0;/* Create our listening socket, bound to the given port. This* is where our clients will connect. */Chat->serversock = createTCPServer(SERVER_PORT);if (Chat->serversock == -1) {perror("Creating listening socket");exit(1);}
}/* Send the specified string to all connected clients but the one* having as socket descriptor 'excluded'. If you want to send something* to every client just set excluded to an impossible socket: -1. */
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {for (int j = 0; j <= Chat->maxclient; j++) {if (Chat->clients[j] == NULL ||Chat->clients[j]->fd == excluded) continue;/* Important: we don't do ANY BUFFERING. We just use the kernel* socket buffers. If the content does not fit, we don't care.* This is needed in order to keep this program simple. */write(Chat->clients[j]->fd,s,len);}
}/* The main() function implements the main chat logic:* 1. Accept new clients connections if any.* 2. Check if any client sent us some new message.* 3. Send the message to all the other clients. */
int main(void) {initChat();while(1) {fd_set readfds;struct timeval tv;int retval;FD_ZERO(&readfds); // 初始化文件描述符集/* When we want to be notified by select() that there is* activity? If the listening socket has pending clients to accept* or if any other client wrote anything. */FD_SET(Chat->serversock, &readfds); // 将文件描述符加入集合for (int j = 0; j <= Chat->maxclient; j++) {if (Chat->clients[j]) FD_SET(j, &readfds);}/* Set a timeout for select(), see later why this may be useful* in the future (not now). */tv.tv_sec = 1; // 1 sec timeouttv.tv_usec = 0;/* Select wants as first argument the maximum file descriptor* in use plus one. It can be either one of our clients or the* server socket itself. */int maxfd = Chat->maxclient;if (maxfd < Chat->serversock) maxfd = Chat->serversock;retval = select(maxfd+1, &readfds, NULL, NULL, &tv);if (retval == -1) {perror("select() error");exit(1);} else if (retval) {/* If the listening socket is "readable", it actually means* there are new clients connections pending to accept. */if (FD_ISSET(Chat->serversock, &readfds)) {int fd = acceptClient(Chat->serversock);struct client *c = createClient(fd);/* Send a welcome message. */char *welcome_msg ="Welcome to Simple Chat! ""Use /nick <nick> to set your nick.\n";write(c->fd,welcome_msg,strlen(welcome_msg));printf("Connected client fd=%d\n", fd);}/* Here for each connected client, check if there are pending* data the client sent us. */char readbuf[256];for (int j = 0; j <= Chat->maxclient; j++) {if (Chat->clients[j] == NULL) continue;if (FD_ISSET(j, &readfds)) {/* Here we just hope that there is a well formed* message waiting for us. But it is entirely possible* that we read just half a message. In a normal program* that is not designed to be that simple, we should try* to buffer reads until the end-of-the-line is reached. */int nread = read(j,readbuf,sizeof(readbuf)-1);if (nread <= 0) {/* Error or short read means that the socket* was closed. */printf("Disconnected client fd=%d, nick=%s\n",j, Chat->clients[j]->nick);freeClient(Chat->clients[j]);} else {/* The client sent us a message. We need to* relay this message to all the other clients* in the chat. */struct client *c = Chat->clients[j];readbuf[nread] = 0;/* If the user message starts with "/", we* process it as a client command. So far* only the /nick <newnick> command is implemented. */if (readbuf[0] == '/') {/* Remove any trailing newline. */char *p;p = strchr(readbuf,'\r'); if (p) *p = 0;p = strchr(readbuf,'\n'); if (p) *p = 0;/* Check for an argument of the command, after* the space. */char *arg = strchr(readbuf,' ');if (arg) {*arg = 0; /* Terminate command name. */arg++; /* Argument is 1 byte after the space. */}if (!strcmp(readbuf,"/nick") && arg) {free(c->nick);int nicklen = strlen(arg);c->nick = chatMalloc(nicklen+1);memcpy(c->nick,arg,nicklen+1);} else {/* Unsupported command. Send an error. */char *errmsg = "Unsupported command\n";write(c->fd,errmsg,strlen(errmsg));}} else {/* Create a message to send everybody (and show* on the server console) in the form:*   nick> some message. */char msg[256];int msglen = snprintf(msg, sizeof(msg),"%s> %s", c->nick, readbuf);/* snprintf() return value may be larger than* sizeof(msg) in case there is no room for the* whole output. */if (msglen >= (int)sizeof(msg))msglen = sizeof(msg)-1;printf("%s",msg);/* Send it to all the other clients. */sendMsgToAllClientsBut(j,msg,msglen);}}}}} else {/* Timeout occurred. We don't do anything right now, but in* general this section can be used to wakeup periodically* even if there is no clients activity. */}}return 0;
}

smallchat-client.c

/* smallchat-client.c -- Client program for smallchat-server.** Copyright (c) 2023, Salvatore Sanfilippo <antirez at gmail dot com>* All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions are met:**   * Redistributions of source code must retain the above copyright notice,*     this list of conditions and the following disclaimer.*   * Redistributions in binary form must reproduce the above copyright*     notice, this list of conditions and the following disclaimer in the*     documentation and/or other materials provided with the distribution.*   * Neither the project name of nor the names of its contributors may be used*     to endorse or promote products derived from this software without*     specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE* POSSIBILITY OF SUCH DAMAGE.*/#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>#include "chatlib.h"/* ============================================================================* Low level terminal handling.* ========================================================================== */void disableRawModeAtExit(void);/* Raw mode: 1960 magic shit. */
int setRawMode(int fd, int enable) {/* We have a bit of global state (but local in scope) here.* This is needed to correctly set/undo raw mode. */static struct termios orig_termios; // Save original terminal status here.static int atexit_registered = 0;   // Avoid registering atexit() many times.static int rawmode_is_set = 0;      // True if raw mode was enabled.struct termios raw;/* If enable is zero, we just have to disable raw mode if it is* currently set. */if (enable == 0) {/* Don't even check the return value as it's too late. */if (rawmode_is_set && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)rawmode_is_set = 0;return 0;}/* Enable raw mode. */if (!isatty(fd)) goto fatal;if (!atexit_registered) {atexit(disableRawModeAtExit);atexit_registered = 1;}if (tcgetattr(fd,&orig_termios) == -1) goto fatal;raw = orig_termios;  /* modify the original mode *//* input modes: no break, no CR to NL, no parity check, no strip char,* no start/stop output control. */raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);/* output modes - do nothing. We want post processing enabled so that* \n will be automatically translated to \r\n. */// raw.c_oflag &= .../* control modes - set 8 bit chars */raw.c_cflag |= (CS8);/* local modes - choing off, canonical off, no extended functions,* but take signal chars (^Z,^C) enabled. */raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);/* control chars - set return condition: min number of bytes and timer.* We want read to return every single byte, without timeout. */raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer *//* put terminal in raw mode after flushing */if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;rawmode_is_set = 1;return 0;fatal:errno = ENOTTY;return -1;
}/* At exit we'll try to fix the terminal to the initial conditions. */
void disableRawModeAtExit(void) {setRawMode(STDIN_FILENO,0);
}/* ============================================================================* Mininal line editing.* ========================================================================== */void terminalCleanCurrentLine(void) {write(fileno(stdout),"\e[2K",4);
}void terminalCursorAtLineStart(void) {write(fileno(stdout),"\r",1);
}#define IB_MAX 128
struct InputBuffer {char buf[IB_MAX];       // Buffer holding the data.int len;                // Current length.
};/* inputBuffer*() return values: */
#define IB_ERR 0        // Sorry, unable to comply.
#define IB_OK 1         // Ok, got the new char, did the operation, ...
#define IB_GOTLINE 2    // Hey, now there is a well formed line to read./* Append the specified character to the buffer. */
int inputBufferAppend(struct InputBuffer *ib, int c) {if (ib->len >= IB_MAX) return IB_ERR; // No room.ib->buf[ib->len] = c;ib->len++;return IB_OK;
}void inputBufferHide(struct InputBuffer *ib);
void inputBufferShow(struct InputBuffer *ib);/* Process every new keystroke arriving from the keyboard. As a side effect* the input buffer state is modified in order to reflect the current line* the user is typing, so that reading the input buffer 'buf' for 'len'* bytes will contain it. */
int inputBufferFeedChar(struct InputBuffer *ib, int c) {switch(c) {case '\n':break;          // Ignored. We handle \r instead.case '\r':return IB_GOTLINE;case 127:           // Backspace.if (ib->len > 0) {ib->len--;inputBufferHide(ib);inputBufferShow(ib);}break;default:if (inputBufferAppend(ib,c) == IB_OK)write(fileno(stdout),ib->buf+ib->len-1,1);break;}return IB_OK;
}/* Hide the line the user is typing. */
void inputBufferHide(struct InputBuffer *ib) {(void)ib; // Not used var, but is conceptually part of the API.terminalCleanCurrentLine();terminalCursorAtLineStart();
}/* Show again the current line. Usually called after InputBufferHide(). */
void inputBufferShow(struct InputBuffer *ib) {write(fileno(stdout),ib->buf,ib->len);
}/* Reset the buffer to be empty. */
void inputBufferClear(struct InputBuffer *ib) {ib->len = 0;inputBufferHide(ib);
}/* =============================================================================* Main program logic, finally :)* ========================================================================== */int main(int argc, char **argv) {if (argc != 3) {printf("Usage: %s <host> <port>\n", argv[0]);exit(1);}/* Create a TCP connection with the server. */int s = TCPConnect(argv[1],atoi(argv[2]),0);if (s == -1) {perror("Connecting to server");exit(1);}/* Put the terminal in raw mode: this way we will receive every* single key stroke as soon as the user types it. No buffering* nor translation of escape sequences of any kind. */setRawMode(fileno(stdin),1); // 获取标准输入流 stdin 的文件描述符/* Wait for the standard input or the server socket to* have some data. */fd_set readfds;int stdin_fd = fileno(stdin);struct InputBuffer ib;inputBufferClear(&ib);while(1) {FD_ZERO(&readfds);FD_SET(s, &readfds);FD_SET(stdin_fd, &readfds);int maxfd = s > stdin_fd ? s : stdin_fd;int num_events = select(maxfd+1, &readfds, NULL, NULL, NULL);if (num_events == -1) {perror("select() error");exit(1);} else if (num_events) {char buf[128]; /* Generic buffer for both code paths. */if (FD_ISSET(s, &readfds)) {/* Data from the server? */ssize_t count = read(s,buf,sizeof(buf));if (count <= 0) {printf("Connection lost\n");exit(1);}inputBufferHide(&ib);write(fileno(stdout),buf,count);inputBufferShow(&ib);} else if (FD_ISSET(stdin_fd, &readfds)) {/* Data from the user typing on the terminal? */ssize_t count = read(stdin_fd,buf,sizeof(buf));for (int j = 0; j < count; j++) {int res = inputBufferFeedChar(&ib,buf[j]);switch(res) {case IB_GOTLINE:inputBufferAppend(&ib,'\n');inputBufferHide(&ib);write(fileno(stdout),"you> ", 5);write(fileno(stdout),ib.buf,ib.len);write(s,ib.buf,ib.len);inputBufferClear(&ib);break;case IB_OK:break;}}}}}close(s);return 0;
}

chatlib.c

#define _POSIX_C_SOURCE 200112L
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/* ======================== Low level networking stuff ==========================* Here you will find basic socket stuff that should be part of* a decent standard C library, but you know... there are other* crazy goals for the future of C: like to make the whole language an* Undefined Behavior.* =========================================================================== *//* Set the specified socket in non-blocking mode, with no delay flag. */
int socketSetNonBlockNoDelay(int fd) {int flags, yes = 1;/* Set the socket nonblocking.* Note that fcntl(2) for F_GETFL and F_SETFL can't be* interrupted by a signal. */if ((flags = fcntl(fd, F_GETFL)) == -1) return -1; // 获取套接字当前的文件状态标志if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1; // 将套接字设置为非阻塞模式/* This is best-effort. No need to check for errors. */setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));return 0;
}/* Create a TCP socket listening to 'port' ready to accept connections. */
int createTCPServer(int port) {int s, yes = 1;struct sockaddr_in sa;if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); // Best effort.memset(&sa,0,sizeof(sa));sa.sin_family = AF_INET;sa.sin_port = htons(port);sa.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY表示服务器将监听所有网络接口上的连接请求if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1 ||listen(s, 511) == -1){close(s);return -1;}return s;
}/* Create a TCP socket and connect it to the specified address.* On success the socket descriptor is returned, otherwise -1.** If 'nonblock' is non-zero, the socket is put in nonblocking state* and the connect() attempt will not block as well, but the socket* may not be immediately ready for writing. */
int TCPConnect(char *addr, int port, int nonblock) {int s, retval = -1;struct addrinfo hints, *servinfo, *p;char portstr[6]; /* Max 16 bit number string length. */snprintf(portstr,sizeof(portstr),"%d",port);memset(&hints,0,sizeof(hints));hints.ai_family = AF_UNSPEC;hints.ai_socktype = SOCK_STREAM;if (getaddrinfo(addr,portstr,&hints,&servinfo) != 0) return -1;for (p = servinfo; p != NULL; p = p->ai_next) {/* Try to create the socket and to connect it.* If we fail in the socket() call, or on connect(), we retry with* the next entry in servinfo. */if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)continue;/* Put in non blocking state if needed. */if (nonblock && socketSetNonBlockNoDelay(s) == -1) {close(s);break;}/* Try to connect. */if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {/* If the socket is non-blocking, it is ok for connect() to* return an EINPROGRESS error here. */if (errno == EINPROGRESS && nonblock) return s;/* Otherwise it's an error. */close(s);break;}/* If we ended an iteration of the for loop without errors, we* have a connected socket. Let's return to the caller. */retval = s;break;}freeaddrinfo(servinfo);return retval; /* Will be -1 if no connection succeded. */
}/* If the listening socket signaled there is a new connection ready to* be accepted, we accept(2) it and return -1 on error or the new client* socket on success. */
int acceptClient(int server_socket) {int s;while(1) {struct sockaddr_in sa;socklen_t slen = sizeof(sa);s = accept(server_socket,(struct sockaddr*)&sa,&slen);if (s == -1) {if (errno == EINTR)continue; /* Try again. */elsereturn -1;}break;}return s;
}/* We also define an allocator that always crashes on out of memory: you* will discover that in most programs designed to run for a long time, that* are not libraries, trying to recover from out of memory is often futile* and at the same time makes the whole program terrible. */
void *chatMalloc(size_t size) {void *ptr = malloc(size);if (ptr == NULL) {perror("Out of memory");exit(1);}return ptr;
}/* Also aborting realloc(). */
void *chatRealloc(void *ptr, size_t size) {ptr = realloc(ptr,size);if (ptr == NULL) {perror("Out of memory");exit(1);}return ptr;
}

参考资料

  1. 超详细的Socket通信原理和实例讲解
  2. 适合初学者的开源Smallchat
  3. nc的基本用法
  4. telnet 使用教程(新手篇)及问题集锦
  5. linux下文件读取性能比较(fread、read、mmap)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/259747.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VNCTF2024misc方向部分wp

文章目录 sqlsharkLearnOpenGLez_msbOnlyLocalSql sqlshark tshark -r sqlshark.pcap -Y "http" -T fields -e frame.len -e http.file_data > data.txt不太像常规的盲注&#xff0c;一次性发送两条很类似的payload&#xff0c;比常规的多了一个least在判断passw…

并发编程-JUC-原子类

JUC 整体概览 原子类 基本类型-使用原子的方式更新基本类型 AtomicInteger&#xff1a;整形原子类AtomicLong&#xff1a;长整型原子类AtomicBoolean &#xff1a;布尔型原子类 引用类型 AtomicReference&#xff1a;引用类型原子类AtomicStampedReference&#xff1a;原子更新…

协调尺度:特征缩放在机器学习中的重要作用

目录 一、介绍 二、背景知识 三、了解功能缩放 四、特征缩放方法 五、特征缩放的重要性 六、实际意义 七、代码 八、结论 一、介绍 特征缩放是机器学习和数据分析预处理阶段的关键步骤&#xff0c;在优化各种算法的性能和效率方面起着至关重要的作用。本文深入探讨了特征缩放的…

LeetCode JS专栏刷题笔记(一)

一、前言 LeetCode 在前不久出了一个 JavaScript 专栏&#xff0c;这个专栏一个目的是为了非前端工程师学习 JS&#xff0c;另一个是为了前端工程师提升 JS 能力。 因此在这个专栏中&#xff0c;基本不涉及什么具体算法问题&#xff0c;都是一些 JS 的入门语法与常见的 JS 面…

【c++ debug】记一次protobuf结构相关的coredump问题

文章目录 1. 问题现象2. 问题描述3. 问题分析4. 问题根因5. 问题修复6. 补充&#xff1a;类成员变量定义为引用类型 1. 问题现象 其中curr_lanes是一个目标上一帧的当前车道current_lanes_curr_lane是lane_id对应的LaneInfo信息现象&#xff1a;在lane_info->lane().success…

【Java面试】MongoDB

目录 1、mongodb是什么&#xff1f;2、mongodb特点什么是NoSQL数据库&#xff1f;NoSQL和RDBMS有什么区别&#xff1f;在哪些情况下使用和不使用NoSQL数据库&#xff1f;NoSQL数据库有哪些类型?启用备份故障恢复需要多久什么是master或primary什么是secondary或slave系列文章版…

多模态基础---BERT

1. BERT简介 BERT用于将一个输入的句子转换为word_embedding&#xff0c;本质上是多个Transformer的Encoder堆叠在一起。 其中单个Transformer Encoder结构如下&#xff1a; BERT-Base采用了12个Transformer Encoder。 BERT-large采用了24个Transformer Encoder。 2. BERT的…

VPX信号处理卡设计原理图:9-基于DSP TMS320C6678+FPGA XC7V690T的6U VPX信号处理卡 信号处理 无线电通信

一、概述 本板卡基于标准6U VPX 架构&#xff0c;为通用高性能信号处理平台&#xff0c;系我公司自主研发。板卡采用一片TI DSP TMS320C6678和一片Xilinx公司Virtex 7系列的FPGA XC7V690T-2FFG1761I作为主处理器&#xff0c;Xilinx 的Aritex XC7A200T作为辅助处理器。XC7A2…

JVS智能BI的ETL数据集实践:数据自动化分析的秘诀

数据集是JVS-智能BI中承载数据、使用数据、管理数据的基础&#xff0c;同样也是构建数据分析的基础。可以通俗地将其理解为数据库中的普通的表&#xff0c;它来源于智能的ETL数据加工工具&#xff0c;可以将数据集进行分析图表、统计报表、数字大屏、数据服务等制作。 数据集管…

ElasticSearch之Index Template 和Dynamic Template

写在前面 在ElasticSearch之Mapping 一文中我们一起看了es的dynamic mapping机制&#xff0c;通过该机制允许我们不需要显式的定义mapping信息&#xff0c;而是es根据插入的文档值来自动生成 &#xff0c;比如插入如下的文档&#xff1a; {"firstName": "Chan…

初识 Rust 语言

目录 前言一、Rust 的背景二、Rust的特性三、部署开发环境&#xff0c;编写一个简单demo1、在ubuntu 20.04部署环境2、编写demo测试 四、如何看待Linux内核引入Rust 前言 自Linux 6.1起&#xff0c;初始的Rust基础设施被添加到Linux内核中。此后为了使内核驱动程序能够用Rust编…

第13章 网络 Page744~746 asio核心类 ip::tcp::endPoint

2. ip::tcp::endpoint ip::tcp::socket用于连接TCP服务端的 async_connect()方法的第一个入参是const endpoint_type& peer_endpoint. 此处的类型 endpoint_type 是 ip::tcp::endpoint 在 在 ip::tcp::socket 类内部的一个别名。 libucurl 库采用字符串URL表达目标的地…

Android开机不显示bootloader界面

Turn it off in the following way LINUX\android\bootable\bootloader\edk2\QcomModulePkg\Library\BootLib\MenuKeysDetection.c 试了没有生效 --- a/QcomModulePkg/Library/BootLib/MenuKeysDetection.cb/QcomModulePkg/Library/BootLib/MenuKeysDetection.c-364,7 364,8…

显微测量|台阶仪二维超精密测量微观形貌

台阶仪通过扫描被测样品表面&#xff0c;获取高分辨率的表面形貌数据&#xff0c;能够揭示微观结构的特征和性能。 标题了解工作原理和性能特点 台阶仪利用扫描探针在样品表面上进行微观测量&#xff0c;通过探测探针和样品表面之间的相互作用力&#xff0c;获取表面形貌信息…

数据分析 — 动画图 pyecharts

目录 一、概念二、安装和导入三、绘图逻辑四、绘图1、柱状图2、折线图3、散点图4、饼图5、南丁格尔图6、Geo() 地理坐标第7、Map() 绘制区域8、词云图9、层叠图10、3D 图11、仪表板 一、概念 Pyecharts 是一个基于 Echarts 的 Python 可视化库&#xff0c;它通过 Python 生成 …

云计算基础-快照与克隆

快照及克隆 什么是快照 快照是数据存储的某一时刻的状态记录&#xff0c;也就是把虚拟机当前的状态保存下来(快照不是备份&#xff0c;快照保存的是状态&#xff0c;备份保存的是副本) 快照优点 速度快&#xff0c;占用空间小 快照工作原理 在了解快照原理前&#xff0c;…

WordPress主题YIA移动端文章页的面包屑不显示怎么办?

平时我们一般都会在文章页导航菜单下方显示面包屑&#xff0c;类似于“当前位置&#xff1a;boke112百科 WordPress 正文”。平时用浏览器调试站点的时候&#xff0c;在Edge浏览器的“切换设备仿真”中&#xff0c;不管是选择什么设备都会显示面包屑。具体如下图所示&#xf…

抓包分析 TCP 协议

TCP 协议是在传输层中&#xff0c;一种面向连接的、可靠的、基于字节流的传输层通信协议。 环境准备 对接口测试工具进行分类&#xff0c;可以如下几类&#xff1a; 网络嗅探工具&#xff1a;tcpdump&#xff0c;wireshark 代理工具&#xff1a;fiddler&#xff0c;charles&…

3分钟了解Android中稳定性测试

一、什么是Monkey Monkey在英文里的含义是猴子&#xff0c;在测试行业的学名叫“猴子测试”&#xff0c;指的是没有测试经验的人甚至是根本不懂计算机的人&#xff08;就像一只猴子&#xff09;&#xff0c;不需要知道程序的任何用户交互方面的知识&#xff0c;给他一个程序&a…

LeetCode刷题计划

LeetCode刷题计划 推荐 代码随想录&#xff1a;https://github.com/youngyangyang04/leetcode-master 卡码网 练习ACM模式 https://kamacoder.com/ 01 #include <iostream> using namespace std;int main() {int a ,b;while(cin>>a>>b){cout<<ab<…