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.mzph.cn/news/690637.shtml

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

相关文章

六、图像的几何变换

文章目录 前言一、镜像变换二、缩放变换 前言 在计算机视觉中&#xff0c;图像几何变换是指对图像进行平移、旋转、缩放、仿射变换和镜像变换等操作&#xff0c;以改变图像的位置、尺寸、形状或视角&#xff0c;而不改变图像的内容。这些变换在图像处理、模式识别、机器人视觉…

更改WordPress作者存档链接author和用户名插件Change Author Link Structure

WordPress作者存档链接默认情况为/author/Administrator&#xff08;用户名&#xff09;&#xff0c;为了防止用户名泄露&#xff0c;我们可以将其改为/author/1&#xff08;用户ID&#xff09;&#xff0c;具体操作可参考『如何将WordPress作者存档链接中的用户名改为昵称或ID…

猪圈Pigsty-PG私有RDS集群搭建教程

博客 https://songxwn.com/Pigsty-PG-RDS/ 简介 Pigsty 是一个更好的本地自建且开源 RDS for PostgreSQL 替代&#xff0c;具有以下特点&#xff1a; 开箱即用的 PostgreSQL 发行版&#xff0c;深度整合地理、时序、分布式、图、向量、分词、AI等 150 余个扩展插件&#xff…

文件IO的lseek以及目录IO

文件IO之 lseek: 1. lseek off_t lseek(int fd, off_t offset, int whence); 功能: 重新设定文件描述符的偏移量 参数: fd:文件描述符 offset:偏移量 whence: SEEK_SET 文件开头 …

基于scrapy框架的单机爬虫与分布式爬虫

我们知道&#xff0c;对于scrapy框架来说&#xff0c;不仅可以单机构建复杂的爬虫项目&#xff0c;还可以通过简单的修改&#xff0c;将单机版爬虫改为分布式的&#xff0c;大大提高爬取效率。下面我就以一个简单的爬虫案例&#xff0c;介绍一下如何构建一个单机版的爬虫&#…

更快找到远程/自由工作的网站

不要使用Fiver或Upwork。 它们已经饱和了。 下面是10个更快找到远程/自由工作的网站&#xff1a; 1. Toptal 这个网站专门为熟练的自由职业者提供远程工作机会&#xff0c;如Shopify和Priceline等一流公司。 他们只接受软件开发、设计和金融等领域的顶级3%自由职业者。 htt…

2024-02-19(Flume)

1.flume中拦截器的作用&#xff1a;个人认为就是修改或者删除事件中的信息&#xff08;处理一下事件&#xff09;。 2.一些拦截器 Host Interceptor&#xff0c;Timestamp Interceptor&#xff0c;Static Interceptor&#xff0c;UUID Interceptor&#xff0c;Search and Rep…

C++集群聊天服务器 nginx+redis安装 笔记 (中)

一、nginx安装 nginx: download 下载nginx安装包 hehedalinux:~/package$ tar -zvxf nginx-1.24.0.tar.gz nginx-1.24.0/ nginx-1.24.0/auto/ nginx-1.24.0/conf/ nginx-1.24.0/contrib/ nginx-1.24.0/src/ nginx-1.24.0/configure nginx-1.24.0/LICENSE nginx-1.24.0/README…

PLC远程监控在制药行业的应用

PLC远程监控在制药行业的应用 制药行业是一个需要高度控制和精确性的行业&#xff0c;而PLC远程监控技术正是这种需求的完美解决方案。PLC远程监控技术是指将传感器、执行器和其他设备连接到PLC系统中&#xff0c;并使用网络和远程访问技术实现对设备的远程监控和控制。下面我…

HarmonyOS4.0系统性深入开发34栅格布局(GridRow/GridCol)

栅格布局&#xff08;GridRow/GridCol&#xff09; 概述 栅格布局是一种通用的辅助定位工具&#xff0c;对移动设备的界面设计有较好的借鉴作用。主要优势包括&#xff1a; 提供可循的规律&#xff1a;栅格布局可以为布局提供规律性的结构&#xff0c;解决多尺寸多设备的动态…

NAS系统折腾记 | TinyMediaManager刮削电影海报

搭建好了NAS系统和Emby Media Server&#xff0c;接下来就是怎样对下载好的电影/电视剧集等内容进行刮削来展示电影海报墙获得更好的效果了。实际上&#xff0c;Emby Server本身就内置了强大的元数据抓取功能&#xff0c;能够自动从互联网上抓取电影、电视剧的元数据和海报等信…

论UI的糟糕设计:以百度网盘为例

上面这一排鼠标一经过就会弹出来&#xff08;不是点才弹出来&#xff09;&#xff0c;然后挡住你的各种操作&#xff0c; 弹出来时你就必须等它消失&#xff0c;卡一下才能操作。 在用户顺畅地操作内容时&#xff0c;经常就卡一下、卡一下、卡一下…… 1、比如鼠标从下到上&am…

基于YOLOv7算法和Widerface数据集的高精度实时人脸检测系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法和Widerface数据集的高精度实时人脸检测系统可用于日常生活中检测与定位人脸目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测…

【springboot+vue项目(十五)】基于Oauth2的SSO单点登录(二)vue-element-admin框架改造整合Oauth2.0

Vue-element-admin 是一个基于 Vue.js 和 Element UI 的后台管理系统框架&#xff0c;提供了丰富的组件和功能&#xff0c;可以帮助开发者快速搭建现代化的后台管理系统。 一、基本知识 &#xff08;一&#xff09;Vue-element-admin 的主要文件和目录 vue-element-admin/ |…

如何确定分库还是 分表?

分库分表 分库分表使用的场景不一样&#xff1a; 分表因为数据量比较大&#xff0c;导致事务执行缓慢&#xff1b;分库是因为单库的性能无法满足要求。 分片策略 1、垂直拆分 水平拆分 3 范围分片&#xff08;range&#xff09; 垂直水平拆分 4 如何解决数据查询问题&a…

【Jvm】性能调优(拓展)Jprofiler如何监控和解决死锁、内存泄露问题

文章目录 Jprofiler简介1.安装及IDEA集成Jprofiler2.如何监控并解决死锁3.如何监控及解决内存泄露(重点)4.总结5.后话 Jprofiler简介 Jprofilers是针对Java开发的性能分析工具(免费试用10天), 可以对Java程序的内存,CPU,线程,GC,锁等进行监控和分析, 1.安装及IDEA集成Jprofil…

车载软件架构 —— Adaptive AUTOSAR软件架构中时间同步、网络管理和软件更新策略

车载软件架构 —— Adaptive AUTOSAR软件架构中时间同步、网络管理和软件更新策略 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成…

解决用IPV6+DDNS访问UNRAID webui周期性失效的问题,smb不能访问的问题

我使用的unraid系统使用ddns&#xff08;DDNSGO&#xff09;绑定域名&#xff08;阿里域名&#xff09;与主机的ipv6地址进行远程访问&#xff0c;unraid是6.12.8。 遇到的问题是&#xff0c;配置当时是没问题的&#xff0c;但是过几天就会失效&#xff0c;无法通过域名访问we…

Maven高级(一)

文章目录 Maven高级&#xff08;一&#xff09;1. 分模块设计与开发1.1 介绍1.2 实践1.2.1 分析1.2.2 实现 1.3 总结 2. 继承与聚合2.1 继承2.1.1 继承关系2.1.1.1 思路分析2.1.1.2 实现 2.1.2 版本锁定2.1.2.1 场景2.1.2.2 介绍2.1.2.3 实现2.1.2.4 属性配置 2.2 聚合2.2.1 介…