标签: linuxc
1.pthread_create函数
函数原型:int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void*(*start_routine)(void *), void *arg);
功能:创建一个新的线程,并将线程加入当前进程
头文件:#include
pthread并非linux系统的默认库,需要手动链接 -线程库 -lpthread
参数:
tid:指向线程标识符的指针
tattr:设置线程属性,可由pthread_attr_init()函数创建默认属性对象
start_routine:线程运行函数的起始地址,注意start_route的返回值地址必须无效
arg:运行函数的参数,arg首选动态堆上分配内存(进程,这样资源可以由程序控制回收),从栈上(线程)分配内存可能导致地址无效或在线程终止时地址被重新分配。
线程函数有多个参数的情况:申明一个结构体来包含所有的参数,然后将结构体传入线程函数。
返回值:
在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果
检测到以下任一情况,pthread_create() 将失败并返回相应的值。
EAGAIN
描述: 超出了系统限制,如创建的线程太多。
EINVAL
描述: tattr 的值无效
2.端口
端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的接口,逻辑端口是逻辑上用于区分服务的端口。
我们写聊天室小项目自然是用的是逻辑端口。
通常一台计算机可能会同时运行多个程序,而他们可能要同时访问网络,比如我现在打开的我的电脑一遍联网播放着网易云音乐(突然有点广告的氛围什么鬼…),一边打开了无数个网页。网易云音乐和我的谷歌浏览器就属于不同进程。
一台主机上不同进程可以绑定到不同端口上,这些进程都可以访问网络而互不干扰。
我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。
tcp/ip将端口号分为两部分,一部分是保留端口即上述知名端口范围为0-1023由权威机构规定其用途,如编号为80的TCP端口由FTP协议专用等,其余的为自由端口,用户进程可以自由申请并使用。
(我能不能说端口号就像是类似于芝麻开门的暗号?只不过有些是“公号”)
可以用“netstat -n”命令,以数字格式显示地址和端口信息。
3.什么是套接字
套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分出来,套接字可以实现将多个客 户连接到一个服务器。
怎么用套接字
int socket(int domain,int type,int protocol);
创建的套接字是一条通信线路的一个端点
命名套接字
要想让通过socket调用创建的套接字可以被其它进程使用,服务器程序就必须给该套接字命名,如下,AF_INET套接字就会关联到一个IP端口号
int bind(int socket,const struct sockaddr *address,size_t address_len);
bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字
创建套接字队列
为了能够在套接字上接受进入链接,服务器程序必须创建一个队列来保存未处理的请求,它用listen系统调用来完成这一工作
int listen(int socket,int backlog);
Linux系统可能会对队列中未处理的连接的最大数目做出限制
接受连接
一旦服务器程序创建并命名套接字之后,他就可以通过accept系统调用来等待客户建立对该套接字的连接
int accept(int socket,struct sockaddr *address,size_t *address_len);
accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字描述符(这个描述符和客户端中描述符是一样等同)
请求连接
客户程序通过一个未命名套接字和服务器监听套接字之间建立的连接的方法来连接到服务器,如下:
int connect(int socket,const struct sockaddr *address,size_t address_len);
参数socket指定的套接字将连接到参数address指定的服务器套接字
关闭套接字
你可以通过调用close函数来终止服务器和客户上的套接字连接
套接字通信
套接字可以通过调用read(),write()系统调用来进行传输数据
4.Client/Server模型
一、
因为tcp是面向连接的,所以在写基于tcp服务器的代码时,要有listen套接字和accept套接字,而基于udp模型的代码,并且udp客户端直接调用 recvfrom/sendto 直接通信即可,不用调用connect函数,这也分别体现出了它们的特性tcp面向连接,而udp则是无需连接。
二、
对于recv在网络通信中,因为tcp是基于字节流的,所以每次recv上来的数据都是一个数据段。可能你在这里发了一个1024字节的数据,到了运输层可能分段,所以对端可能不会一次性对上来1024字节的数据。所以由此看出来recv每次读socket文件时,都读的是一个数据段。
解决方案:封装函数recv,自定义一个读取数据的函数my_recv
/*
*函数名:my_recv
*描述:从套接字上读取一次数据(以'\n'为结束的标志)
*参数:conn_fd ————从该连接套接字上接收数据
* data_buf ————读取到的数据保存到此缓存中
* len ————data_buf所指向的空间长度
*返回值:出错返回-1,服务器已关闭连接则返回0,成功返回读取的字节数
*/
int my_recv(int conn_fd, char *data_buf, int len)
{
static char recv_buf[BUFSIZE]; //自定义缓冲区,BUFFSIZE定义在my_recv.h中
static char +pread; //指向下一次读取数据的位置
static int len_remain = 0; //自定义缓冲区中剩余字节数
int i;
//如果自定义缓冲区中没有数据,则从套接字读取数据
if(len_remain <= 0)
{
if((len_remain = recv(conn_fd, recv_buf, sizeof(recv_buf),0))<0)
my_err("recv",__LINE__);
else if(len_remain == 0)//目的计算机的socket连接关闭
return 0;
pread = revc_buf; //重新初始化pread指针
}
//从自定义缓冲区中读取一次数据
for(i=0; *pread != '\n'; i++)
{
if(i>len) //防止指针越界
return -1;
data_buf[i] = *pthread++;
len_remain--;
}
//去除结束标志
len_remain--;
pread++;
return i; //读取成功
}
实际上就是将套接字缓冲区的数据拷贝到自定义缓冲区,再按照格式(本代码是以’\n’为结束标志)读取数据。
当然也可以自己在发送时自己选择定义结束标志,目的是为了发送方与接收方都能知道约定发送的包到哪里结束了,到底这个包接收的是否完整。
三、
对于阻塞socket而言,send调用的时候,当我们把应用层的数据拷贝到内核缓冲区的时候,如果内核缓冲区已满,那么就会阻塞,从send 函数返回也并不代表,数据发送到了对端,只是代表了数据已经拷贝到了内核缓冲区。
四、
send成功返回,对于 TCP来讲只是应用层数据被拷贝到了内核发送缓冲区中,对于 UDP来讲只是数据被加入到了 数据链路层队列中。
五、
connect调用失败后,并不可以直接重用该socket,重用前需 close再用,因为基于 Tcp状态转换图得知 , connect 函数其实就是三次握手,那么当三次握手失败的话,socket 不是处于 CLOSED 而是SYN_SENT状态,所以我们必须重新关闭才能再次使用该 socket。
六、
标题如何实现文件传输,如果文件较大如何解决
- 服务器端 把大文件分包 每一个包大小建立一个socket(对应一个线程)进行传输
2.客户端 对每一个包对应一个线程(同时对应一个socket)进行接收
3.在发送每一个包时 包前要发送这个包的大小 和对应的偏移量
4.在32位系统中要开启大文件的宏开关
5.利用pread pwrite 可以保证原子操作
七、
有时我们不想Server主动断开连接后由于time_wait,而不能立即重启,所以就想重新绑定相同的地址的Server。
那么为了端口复用需满足以下条件其中之一:
1.socket绑定的不是同一网卡可以绑定
2.设置了 SO_REUSEADDR并且不能是LINTEN状态的节点
可见如果是处于time_wait的节点,我们只需提前设置 SO_REUSEADDR即可端口复用,解决Server不能立刻重启。
八、终止连接函数
int close(int socketfd)
首先如果我们要正常终止连接的话就需要调用close函数,调用时只有对应的socketfd 对应的文件描述符对应的 file struct 结构体的引用计数为1的时候,调用才会触发正常的四次挥手否则只是引用计数减1.
九、 close函数在服务器上需要注意的点:
1.多进程程序别忘了在fork之后,父进程关闭 accept的返回的socket,子进程别忘了关闭listensocket,否则会因为引用计数的原因无法正常触发终止序列。(FIN)
2.收到FIN段后需要即使调用close函数去关闭该socket,否则多路复用的机制(epoll)一直会提示该socket读就绪。
(感觉没写完留个坑…)