项目总结--聊天室


项目简介:采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室!

OS:Ubuntu 15.04

IDE:vim gcc make

DB:Sqlite 3

Time:2015-12-09 ~ 2012-12-21

项目功能架构:

采用client/server结构;
给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出);
多客户可同时连接服务器进行自己操作;
部分操作如下图所示: 
Make命令: 
 
1. 多用户注册: 
(1)服务器监听终端 
 
(2)用户注册终端 


多用户登录、聊天: 
(1)用户yy登录 
 
 
(2)用户rr登录 
 
(3)服务器监听终端 

程序结构
公共数据库
chatRome.db

服务器端


server.c:服务器端主程序代码文件;
config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明);
config.c:服务器端公共函数的实现文件;
list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作;
register.c:服务器端实现用户注册;
login.c:服务器端实现用户登录;
chat.c:服务器端实现用户的聊天互动操作;
Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server
客户端
 
1. client.c:客户端主程序代码文件; 
2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明); 
3. config.c:客户端公共函数的实现文件; 
4. register.c:客户端实现用户注册; 
5. login.c:客户端实现用户登录; 
6. chat.c:客户端实现用户的聊天互动操作; 
7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client; 
8. interface.c:客户端界面文件;
--------------------- 
作者:逆風的薔薇 
来源:CSDN 
原文:https://blog.csdn.net/fly_yr/article/details/50378697 
版权声明:本文为博主原创文章,转载请附上博文链接!


1、makefile的作用是什么?

makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile带来的好处就是--“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。

makefile文件保存了编译器和连接器的参数选项,还表述了所有源文件之间的关系(源代码文件需要的特定的包含文件,可执行文件要求包含的目标文件模块及库等)。创建程序(make程序)首先读取makefile文件,然后再激活编译器,汇编器,资源编译器和连接器以便产生最后的输出,最后输出并生成的通常是可执行文件.创建程序利用内置的推理规则来激活编译器,以便通过对特定CPP文件的编译来产生特定的OBJ文件. 

Tip:在Makefile中的命令,必须要以[Tab]键开始

2、 数据库与文件进行数据存储有哪些区别?

(1)文件系统用文件将数据长期保存在外存上,数据库系统用数据库统一存储数据。

(2)文件系统中的程序和数据有一定的联系,数据库系统中的程序和数据分离。

(3)文件系统用操作系统中的存取方法对数据进行管理,数据库系统用DBMS统一管理和控制数据。

(4)文件系统实现以文件为单位的数据共享,数据库系统实现以记录和字段为单位的数据共享。

3、系统调用与库函数的区别

(1)库函数是语言或应用程序的一部分,而系统调用是内核提供给应用程序的接口,属于系统的一部分

(2)库函数在用户地址空间执行,系统调用是在内核地址空间执行,库函数运行时间属于用户时间,系统调用属于系统时间,库函数开销较小,系统调用开销较大

(3)库函数是有缓冲的,系统调用是无缓冲的

(4)系统调用依赖于平台,库函数并不依赖

函数库调用

系统调用

平台移植性好

依赖于内核,不保证移植性

调用函数库中的一段程序(或函数)

调用系统内核的服务

一个普通功能函数的调用

是操作系统的一个入口点

在用户空间执行

在内核空间执行

它的运行时间属于“用户时间”

它的运行时间属于“系统”时间

属于过程调用,调用开销较小

在用户空间和内核上下文环境间切换,开销较大

库函数数量较多

UNIX中大约有90个系统调用,较少

典型的C函数库调用:printf scanf malloc 

典型的系统调用:fork open write 

4、为什么选择系统调用?什么时候使用系统调用操作文件?

(1)why

使用系统调用是因为系统资源的有限性以及内核管理的方便,系统调用将上层内的应用开发与底层的硬件实现分开,上层应用不需要关注底层硬件的具体实现。Linux的系统调用使用软中断实现,使用系统调用后,该程序的状态将从用户态切换到内核态。库函数实现最终也要调用系统调用函数,但它封装了系统调用操作,从而增加了代码的可移植性。

(2)when

每一个运转的程序——也叫进程——都会拥有一些相关联的文件描述符(file descriptor)。这是些不大的整数,可以用来使用打开了的文件或者设备。有多少个文件描述符可以使用,取决于系统如何配置。

文件操作系统调用
1)创建
int creat(const char *filename,mode_t mode);
2)打开
int open(const char *filename,int flags);
如果flags使用了O_CREATE标志则使用
int open(const char *filename,int flags,mode_t mode);
3)读写
int read(int fd,const void *buf,size_t length);
int write(int fd,const void *buf,size_t length);
4)定位
int lseek(int fd,offset_t offset,int whence);
whence可为一下值:
SEEK_SET:相对文件开头
SEEK_CUR:相对于文件读写指针的当前位置
SEEK_END:相对于文件末尾
5)关闭
int close(int fd);

5、为什么选择库函数?什么时候使用库函数操作文件?

(1)利用库函数操作文件具有跨平台的作用;

库函数对文件的操作实际上是通过系统调用来实现的。例如 C库函数 fwrite() 就是通过 write() 系统调用来实现的。

但是使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?

这是因为,读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言)。这时,使用库函数就可以大大减少系统调用的次数。这一结果又源于缓冲区技术,在用户空间和内核空间对文件操作都使用了缓冲区。

例如:用 fwrite() 写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

(2)操作

***库函数 - 读文件

size_t fread(void *ptr, size_t size, size_t n, FILE *stream)

功能:从stream指向的文件中读取n个字段,每个字段为size字节,并将读取的数据放入ptr所指向的字符数组中,返回实际已读取的字节数。(读出来的数据量为size*n)

***库函数 - 写文件

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream)

功能:从缓冲区ptr所指向的数组中把n个字段写到stream指向的文件中,每个字段长为size个字节,返回实际写入的字段数。

***库函数 - 创建和打开

FILE *fopen(const char *filename, const char *mode)

filename:打开的文件名(包含路径,缺省为当前路径)

mode:打开模式

6、为什么数据库选择sqlite?还有那些嵌入式数据库?特点是什么?

(1)SQLITE功能简约,小型化,追求最大磁盘效率;只是单机上用的,数据量不是很大,需要方便移植或者需要频繁读/写磁盘文件的话,就用SQLite比较合适.

(2) 嵌入式数据库及其特点

**************************SQLite**************************

主页:http://www.sqlite.org

SQLite诞生于2000年5月,这几年增长势头迅猛无比,目前版本是3.3.8。

SQLite的特点如下:

1、无需安装配置,应用程序只需携带一个动态链接库。

2、非常小巧,For Windows 3.3.8版本的DLL文件才374KB。

3、ACID事务支持,ACID即原子性、一致性、隔离性、和持久性(Atomic、Consistent、Isolated、和 Durable)。

4、数据库文件可以在不同字节顺序的机器间自由的共享,比如可以直接从Windows移植到Linux或MAC。

5、支持数据库大小至2TB。

**************************Berkeley DB**************************

主页:http://www.oracle.com/database/berkeley-db/index.html

Berkeley DB是由美国Sleepycat Software公司开发的一套开放源码的嵌入式数据库的程序库,它于1991年发布,号称“为应用程序开发者提供工业级强度的数据库服务”,可谓是老牌悍将。Sleepycat现已被甲骨文(ORACLE)公司收购。

Berkeley DB的特点如下:

1、嵌入式,无需安装配置。

2、为多种编程语言提供了API接口,其中包括C、C++、Java、Perl、Tcl、Python和PHP等等。

3、轻便灵活。它可以运行于几乎所有的UNIX和Linux系统及其变种系统、Windows操作系统以及多种嵌入式实时操作系统之下。

4、可伸缩。它的Database library才几百KB大小,但它能够管理规模高达256TB的数据库。它支持高并发度,成千上万个用户可同时操纵同一个数据库。

**************************Firebird嵌入服务器版(Embedded Server)**************************

主页:http://www.firebirdsql.org

从Interbase开源衍生出的Firebird,充满了勃勃生机。虽然它的体积比前辈Interbase缩小了几十倍,但功能并无阉割。为了体现Firebird短小精悍的特色,开发小组在增加了超级服务器版本之后,又增加了嵌入版本,最新版本为2.0。

Firebird的嵌入版有如下特色:

1、数据库文件与Firebird网络版本完全兼容,差别仅在于连接方式不同,可以实现零成本迁移。

2、数据库文件仅受操作系统的限制,且支持将一个数据库分割成不同文件,突破了操作系统最大文件的限制,提高了IO吞吐量。

3、完全支持SQL92标准,支持大部分SQL-99标准功能。

4、丰富的开发工具支持,绝大部分基于Interbase的组件,可以直接使用于Firebird。

5、支持事务、存储过程、触发器等关系数据库的所有特性。

6、可自己编写扩展函数(UDF)。

七、多进程与多线程的区别?

对比维度

多进程

多线程

总结

数据共享、同步

数据共享复杂,需要用IPC;数据是分开的,同步简单

因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂

各有优势

内存、CPU

占用内存多,切换复杂,CPU利用率低

占用内存少,切换简单,CPU利用率高

线程占优

创建销毁、切换

创建销毁、切换复杂,速度慢

创建销毁、切换简单,速度很快

线程占优

编程、调试

编程简单,调试简单

编程复杂,调试复杂

进程占优

可靠性

进程间不会互相影响

一个线程挂掉将导致整个进程挂掉

进程占优

分布式

适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单

适应于多核分布式

进程占优

注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受

八、什么时候采用多线程?什么时候用多进程?

1)需要频繁创建销毁的优先用线程

原因请看上面的对比。

这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

2)需要进行大量计算的优先使用线程

所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

这种原则最常见的是图像处理、算法处理。

3)强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程

原因请看上面对比。

5)都满足需求的情况下,用你最熟悉、最拿手的方式

至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

九、进程间通信方式及比较

1、管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

2、有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3、信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

4、消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5、 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6、共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

7、 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

十、进程和线程通信方式的区别

1、拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。同一进程中的所有线程都具有相同的地址空间。

2、调度的基本单位:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。

3、系统开销:由于创建或撤销进程时,系统都要分配或回收资源,如内存空间、I/O 设备等,OS所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。

4、通信方面:进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。

5、并发性:不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行,甚至允许在一个进程中的所有线程都能并发执行。同样,不同进程中的线程也能并发执行。

6、独立性:在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低。

十一.谈谈你对进程的理解,内核是如何管理进程的?
进程是计算机中处于运行中的程序的实体
线程是最小的运行单位,而进程是线程的容器
程序本身只是指令、数据及其组织形式的描述,进程才是程序(指令和数据)的真正运行实例
1.内核将程序读入内存,为程序分配内存空间                //为程序分配

2.内核为该进程分配进程标识符PID和其他所需资源           //为进程分配

3.内核为进程保存PID及相应的状态信息,把进程放到运行队列中等待执行,程序转化为进程后可以被操作系统的调度程序调度执行了

每个进程在系统中都有一个唯一的ID标识它,这个ID就是进程标识符(PID)。因为其唯一性,所以系统可以根据PID准确定位到一个进程
进程标识符的类型为pid_t,本质上是一个无符号整形的类型别名
程序是可运行的二进制代码文件,把这种文件加载到内存中运行就得到了一个进程(所以你每打开一个程序都会占用一部分系统内存)
同一个程序文件可以被加载多次成为不同的进程(你可以打开两个QQ)。进程与进程标识符之间是一对一的关系,而程序与进程之间是一对多的关系
 一个进程的可能状态有如下几种:

    1)  运行态——已经获得了资源,并且进程正在被 CPU 执行。进程既可运行在内核态,也可运行在用户态。

    2)  就绪态——当系统资源已经可用,但由于前一个进程还没有执行完释放  CPU,准备进入运行状态。

    3)  可中断睡眠状态——当进程处于可中断等待状态时,系统不会调度该程序执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以被唤醒进入就绪状态或者运行态。

    4)  不可中断睡眠状态——处于中断等待状态,但是该进程只能被使用  wake_up()函数明确唤醒的时候才可进入就绪状态。

    5)  暂停状态——当进程收到 SIGSTOP、SIGSTP、SIGTTIN 或者 SIGTTOU 就会进入暂停状态,收到 SIGCONT 信号即可进入运行态。

    6)  僵死态——进程已经停止运行,但是其父进程还没有询问其状态。

十二、 什么是进程死锁?操作进程死锁的原因是什么?
(1)所谓的死锁是指在多道程序系统中,一组进程中的每个进程都无限期地等待被该组进程中的另一个进程所占有且永远不会被释放的资源,这种现象称系统处于死锁状态,简称死锁。处于死锁状态的进程成为死锁进程。 
  系统发生死锁会大量浪费系统资源甚至会导致整个系统崩溃。

(2)产生死锁的原因主要有两个:

一是竞争资源,系统提供的资源有限,不能满足每个进程的需求;
二是多道程序运行时,进程的推进顺序不合理。
产生死锁的必要条件
互斥条件:进程对所分配到的资源进行排它性使用,在一段时间内某资源只由一个进程占用。
请求和保持条件:指一个进程已经保持了至少一个资源,但又提出新的资源请求,而此资源被其他占用,此时请求进程阻塞,但又对自己已经占用的资源保持不放。
不剥夺条件:进程已经获得资源,在未使用完之前,不能被剥夺,只能在使用完时,由进程自己释放。
环路等待条件:发生死锁时,必然存在一个进程-资源的环形链
注:系统的资源分为两类:

永久性资源(可重生资源),是指那些可供进程重复利用、长期存在的资源,如内存、CPU等硬件资源,以及数据文件、共享程序代码等软件资源。

临时性资源(消耗性资源),是指由某个进程产生、只为另一个进程使用一次、或经过短暂时间后便不可再使用的资源,如 I/O 和时钟中断、消息等。 
  两种资源都可能导致发生死锁。

处理死锁的方法
l  预防死锁

事先预防,破坏产生死锁的四个必要条件之一。

摒弃“请求和保持”条件:
进程在申请资源时,是一次性的。

如何摒弃“请求“:当进程来时,一次性分配所有的资源(如果系统满足),这样就不会再有”请求“了。

如何摒弃“保持“:只要有一个资源得不到分配,也不给这个进程分配其他的资源。

摒弃“不剥夺”条件:在这种方法中,进程是逐个提出对资源的要求的。如果一个进程,获得了部分资源,但得不到其它资源,这时,它释放自己所占用的资源。
摒弃“环路等待”条件:把资源排序,当进程申请资源时,按序申请。
l  避免死锁

事先预防,并不是破坏产生死锁的四个必要条件,而是用某种方法去防止系统进入不安全状态,目前在较完善的系统中,常用此方法。银行家算法

安全状态:是指系统能按照某种进程顺序(P1,P2,…Pn),来为每一个进程Pi分配其所需要的资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利地完成。如果系统无法找到这样一个安全序列,则称系统处于不安全状态。

l  检测死锁

并不事先采取任何限制性的措施,也不必检查系统是否已经进入不安全区,此方法允许发生死锁,关键是,发生死锁了,系统可以通过检测机构发现死锁,并精确确定与死锁有关的进程和资源,然后,采取适当措施,从系统中将已经发生的死锁清除

l  解除死锁

这是与检测死锁配套使用。当检测到系统已经发生了死锁,要将进程从死锁状态中解脱出来。常用的方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已经处于阻塞状态的进程,使之转为就绪状态,以继续运行。

十三.保持进程同步的方法有哪些?
Linux 下:

Linux 下常见的进程同步方法有:SysVIPC 的 sem(信号量)、file locking / record locking(通过 fcntl 设定的文件锁、记录锁)、futex(基于共享内存的快速用户态互斥锁)。针对线程(pthread)的还有 pthread_mutex 和 pthread_cond(条件变量)。
 
Linux 下常见的进程通信的方法有 :pipe(管道),FIFO(命名管道),socket(套接字),SysVIPC 的 shm(共享内存)、msg queue(消息队列),mmap(文件映射)。以前还有 STREAM,不过现在比较少见了(好像)。

Windows下:
在Windwos中,进程同步主要有以下几种:互斥量、信号量、事件、可等计时器等几种技术。

在Windows下,进程通信主要有以下几种:内存映射、管道、消息等,但是内存映射是最基础的,因为,其他的进程通信手段在内部都是考内存映射来完成的。

十四.线程同步方式有哪些?优缺点?
现在流行的进程线程同步互斥的控制机制,其实是由最原始,最基本的4中方法实现的:

临界区:通过多线程的互串行访问公共资源或一段代码,速度快,适合控制数据访问。
互斥量:为协调共同对一个共享资源的单独访问而设计。只有拥有互斥对象的线程才有权限去访问系统的公共资源,因为互斥对象只有一个,所以能够保证资源不会同时被多个线程访问。
信号量:为控制一个具有有限数量的用户资源而设计。它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数。
事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
 

====================================
       LINUX聊天室50问
====================================


1.问题:怎么实现多个终端?监听?

2.一个聊天室程序,到底会分解成哪一些步骤呢?

3.开启一个 Socket,绑定到另一个端口,并且一直监听此端口。

4.服务器是得有很多个SOCKET,还是一个socket,而有很多个port?
答:是有很多个socket,并用数组管理

5.服务器(SOCKET数组):基本上是多线程:一个线程监听,Accept之后创建一个线程处理请求

————————————————————————————————————
思路: 
1:服务器端有个Listener线程,用来监听。 
2:服务器端再来个server   socket线程,用来和客户端连接。 
3:当监听到客户连接时,创建server   socket线程,独立和客户端连接。 
4:服务器端要有一个结构来保存客户端信息。可以自己写一个类来封装。如用户ip等。 
——————————————————————————————————————

6.当监听到客户连接时,创建server   socket线程,独立和客户端连接。 
这么说,socket是可以动态创建的。

7.监听listening和connect之间,是一个配对的关系。

8.那么请问bind呢?socket和port与bind?
答:估计是将创建的socket与本地IP绑定起来。

9.想想看,QQ有几亿个用户,他是怎么管理的?如何回快QQ的登陆呢?
我想问一下,终端侦听的客户端有限制么?是不是客户端越多,开的线程就越多,程序处理速度就越慢

呢?我要做的至少要连接500个客户端,进行数据的交互,会不会有问题呢。再顶下自己~~

10.ServerSocket.vb 
负责监听是否有客户端连接,开线程接受客户端Socket

————————————————————————————————————
        Public   Svr   As   IPEndPoint                   '服务器地址簇 
        Public   SvrListenSocket   As   Socket   '服务器侦听socket 
        Public   SvrListener   As   Socket           '表示异步操作的状态 
        Public   SvrListenThread   As   Thread   '服务器侦听线程 
        Public   Done   As   New   ManualResetEvent(True)   '通知一个或多个正在等待的线程已

发生事件 
Public   alsock   As   New   ArrayList       '放置socket的队列 
——————————————————————————————————————

11.  '侦听50个客户端   并启动线程 
 '开始一个异步操作来接受一个传入的连接尝试 
                                SvrListenSocket.BeginAccept(New   AsyncCallback(AddressOf  

AcceptCallback),   SvrListenSocket)

12.当发现一个连接后,建立一个线程,然后处理,当另外一个来后,重新创建!
public   sealed   class   Sockets 

      private   Sockets() 
      { 
              //在这里可以建立连接 
      }

      public   static   Sockets   GetInstance(IPEndPoint   point) 
      { 
              //在这里可以建立线程并连接 
      } 
}


13.我的那个例程是用进程还是线程实现通信的?不知啊。是socket。但是?

14.建立多个线程就ok了 为每一个连接 创建一个线程去处理
      while (true)
            {
                // 得到包含客户端信息的套接字
                Socket client = server.Accept();

                //创建消息服务线程对象
                ClientThread newclient = new ClientThread(client);

                //把ClientThread 类的ClientService方法委托给线程
                Thread newthread = new Thread(new ThreadStart(newclient.ClientService));

                // 启动消息服务线程
                newthread.Start();
            }

15.客户端在通信时,需要知道服务器的IP地址。属于主动的一方
16.客户端在向服务器(系统)请求一个临时端口,用完后释放端口,断开链接。需要时再重新连上。
那请问,QQ打开一个窗口时,是一直连接着吗?(应该是的。如果没有打开窗口,那就没连接。只是跟

服务器连接着)

17.服务器端,在通信时一般属于被动方,不知客户端的IP和端口信息。

18.用connect将客户端套接字与服务器特定地址进行绑定连接

19.将socket连接到远程主机(client. c)或者本地IP(server.c)

20.按要求通过socket接收和发送数据

21.端口地址是以进程为单位分配的。发送和接收必须使用同一端口。

22.为什么在一个服务器对多个客户端情况下,进程会阻塞,而线程就不阻塞了呢?
每个客户端对应两个线程(收发各一个);而客户端本身呢,只需要有两个。

23.发送请求过程
1)利用gethostbyname()获取主机信息
2)初始化socket端口
3)利用connect函数将自己的ip地址等信息发送给主机,等待主机调用accept函数来接受请求。

24.主机接收请求,进行数据通信
1)主机用accept接收请求
2)创建子线程,显示欢迎信息
3)接收返回信息,显示连接成功
4)关闭客户端socket
5)关闭服务端socket,结束子线程。

24.define BACKLOG 10  这个有何用?

25.那我Lisent只能监听一个啊?那么多个客户端,怎么办呢?怎么实现并发监听呢?

 首先,在写之前一定要想好服务器和客户端之间的协议,一开始的时候没有想好协议,走了不少弯路。然后,服务器在接收到用户登录是要记录在线用户的数量和其名字,这个方面,我用了一个结构体usermanger,其中包含一个在线的标志位和用户的信息,但是我快做完的时候,其他人告诉我,这个方面可以用一个循环链表存储,用户登录的时候就是头插法插入一个结点,发送信息的时候,遍历链表然后发送,群聊就是遍历链表除了自己外,都发送一次,但是,那个时候,我的聊天室已经成形了,所以我打算将这种方法修改完整,完成后再写链表那种版本的。
 

猜你喜欢

转载自blog.csdn.net/qq_37050329/article/details/83145349