AI智能
改变未来

基于多线程的TCP并发服务器的创建及相关函数

线程:有时又称轻量级进程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,它是进程中的一个实体一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含一点必不可少的资源。

优势:

  1. 在多处理器中开发程序的并行性
  2. 在等待慢速IO操作时,程序可以执行其他操作,提高并发性
  3. 模块化的编程,能清晰的表达程序中独立事件的关系,结构清晰
  4. 占用较少的系统资源

线程创造:获取ID
线程控制:终止、连接、取消、发送信号、清除操作
线程同步:互斥量、读写锁、条件变量
线程高级控制:一次性初始化、线程属性、同步属性、私有数据、安全的fork

TCP服务器创建:
  socket(套接字)实质上提供了进程通信的端点,进程通信之前,双方首先必须有各自的一个端点,否则是没有办法通信的。通过套接字将IP地址和端口绑定之后,客户端就可以和服务器通信了。
  当我们访问套接字时,要像访问文件一样使用文件描述符。

  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序,该函数返回一个类似于文件描述符的句柄。

socket函数: 插座创造一个套接字
原函数:int socket(int domain,int type,int protocol)
头文件的:#include <SYS / socket.h>中
参数:domain,通信域,确定通信特性,包括地址格式域描述
   type,套接字类型
   protocol,指定相应的传输协议
返回值:成功则返回套接字文件描述符,失败返回-1

参数域:通信域,确定通信特性,包括地址格式域描述

描述
AF_INET IPv4的因特网域
AF_INET6 IPv6的的因特网域
AF_UNIX UNIX域
AF_UNSPEC 未指定

参数类型:套接字类型类型描

类型 描述
SOCK_DGRAM 长度固定的,无连接的不可靠报文传输
SOCK_RAW IP协议的数据报接口
SOCK_SEQPACKET 长度固定,有序,可靠的面向连接报文传递
SOCK_STREAM 有序,可靠,双向的面向连接的字节流

参数协议,指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把协议设置为0来使用这个默认的值。

  在socket程序设计中,struct sockaddr_in(或者struct sock_addr)用于记录网络地址

struct sockaddr_in
{
 short int sin_family; / 协议族 /
 unsigned short int sin_port; / 端口号 /
 struct in_addr sin_addr; / 协议特定地址 /
 unsigned char sin_zero [8]; / *填0 * /
}
typedef struct in_addr {
 union {
  struct {
   unsigned charwers1,
   shield2,
   shield3,
   shield4;
  } S_un_b;
  struct {
   unsigned short s_w1,
   s_w2;
  } S_un_w;
  unsigned long s_addr;
 } S_un;
} IN_ADDR;

  IP地址通常由数字加点(192.168.0.1)的形式表示,而在结构组in_addr中使用的IP地址是由32位的整数表示的,为了转换我们可以使用下面两个函数:
int inet_aton(const char *cp,struct in_addr *inp)

char *inet_ntoa(struct in_addr in)

  函数里面a代表ascii,n代表网络 .inet_aton是将abcd形式的IP转换为32位的IP,存储在inp指针里面。inet_ntoa是将32位IP转换为ABCD的格式。

  不同类型的CPU对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要是统一的。所以当内部字节存储顺序和网络字节序(big endian)不同时,就一定要进行转换。

字节序转换,32位的整数(0x01234567)
小端字节序
大端字节序
htons to unsigned short类型从主机序转换到网络序
htonl把unsigned long类型从主机序转换到网络序
ntohs把unsigned short类型从网络序转换到主机序
ntohl把unsigned long类型从网络序转换到主机序

bind函数: 结合绑定服务器的地址和端口到插座
原函数:int bind(int sockfd,const struct sockaddr * addr,socklen_t len)
头文件:#include <sys / socket.h>
参数:sockfd,服务器插槽
   addr,服务器的地址
   len,地址的长度
返回值:成功返回0,失败返回-1

参数地址:服务器的地址,对于因特网域,如果设置地址为INADDR_ANY,套接字可以绑定到所有的网络端口。这意味着可以收到这个系统所有网卡的数据包一般我们。在使用SOCKADDR_IN类型的结构体代替的sockaddr行业释义体系结构

connect函数: 面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接。
原函数:int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
参数:sockfd,socket函数返回的socket描述符;
   serv_addr,包含远端主机IP地址和端口号的针;
   addrlen,是远端地质结构的长度。
返回值:出现错误时返回-1,并且设置errno为相应的错误码。

listen函数: 设置允许的最大连接数,Listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
原函数:int listen(int sockfd,int backlog)
头文件:#include <sys / socket.h>
参数:sockfd,服务器插槽
   backlog,用于表示服务器能接受的请求数量
返回值:成功返回0,失败返回-1
注: 服务器调用听函数来宣告可以接受连接请求

accept函数: 接受等待来自客户端的连接请求,一旦服务器调用了听,套接字就能接收连接请求。使用接受函数来接受并建立请求。
原函数:int accept(int sockfd,struct sockaddr * restrict addr,socklen_t * restrict len)
头文件:#include <SYS / socket.h>
参数:sockfd,服务器插槽
   addr,用来存放客户端的地址,如果地址的空间足够大,系统会自动填充。
   len,地址的长度
返回值:成功则返回套接字描述符,失败返回-1

注:

  1. 接受返回一个新的socket关联到客户端,它与原始的socket有相同的套接字类型和协议族。传递给接受的原始socket并没有关联客户端,它要继续保持可用状态,接收其他请求。
  2. 接受是一个阻塞的函数,会一直等到有客户端的请求。

收发数据,用函数recv()、send()/sendto()或者read()、write()

send函数:
原函数:int send(int sockfd, const void *msg, int len, int flags);
参数:sockfd,用来传输数据的socket描述符;
   msg,指向要发送数据的指针;
   len,以字节为单位的数据的长度;
   flags,一般情况下置为0(关于该参数的用法可参照man手册)。
返回值:返回实际上发送出的字节数,可能会少于你希望发送的数据。
注: 在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时,应该对这种情况进行处理。

recv()函数:
原函数:int recv(int sockfd,void *buf,int len,unsigned int flags);
参数:sockfd,接受数据的socket描述符;
   buf ,存放接收数据的缓冲区;
   len,缓冲的长度;
   flags,一般情况下置为0。
返回值:返回实际上接收的字节数,当出现错误时,返回-1并置相应的errno值。

sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。 由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。

sendto()函数:
原函数:int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
参数:sockfd,用来传输数据的socket描述符;
   msg,指向要发送数据的指针;
   len,以字节为单位的数据的长度;
   flags,一般情况下置为0(关于该参数的用法可参照man手册)。
   to,目地机的IP地址和端口号信息;
   tolen,常常被赋值为sizeof (struct sockaddr);
返回值:返回实际发送的数据字节长度或在出现发送错误时返回-1。

recvfrom()函数:
原函数:int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
参数:sockfd,接受数据的socket描述符;
   buf ,存放接收数据的缓冲区;
   len,缓冲的长度;
   flags,一般情况下置为0。
   from,struct sockaddr类型的变量,该变量保存源机的IP地址及端口号;
   fromlen,常置为sizeof (struct sockaddr),当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。
返回值:返回接收到的字节数或当出现错误时返回-1,并置相应的errno。

关闭网络连接,close()
  当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
  close(sockfd);

  也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。

shutdown函数:
原函数:int shutdown(int sockfd,int how);
参数:sockfd,需要关闭的socket的描述符;
   how,允许为shutdown操作选择以下几种方式:
      0——-不允许继续接收数据
      1——-不允许继续发送数据
      2——-不允许继续发送和接收数据,
      均为允许则调用close ()
返回值:操作成功时返回0,在出现错误时返回-1并置相应errno。 、

server

#include <pthread.h>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <errno.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>/*1,int socket(int domain,int type,int protocol)//创建一个套接字2,int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)//绑定地址3,int listen(int sockfd,int backlog)//设置臭接收4,int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)//允许接收5,read,write*/#define MAX_LISTEN 10char buf[100];int ad[10];struct sockaddr_in server_ip,remote_ip;void *thread_fun(void *arg){while(1){//持续的读取数据printf(\"read data from client:%s\\n\",inet_ntoa(remote_ip.sin_addr));read(ad[(int)arg],buf,100);printf(\"buf is %s\\n\",buf);memset(buf,0,100);}return NULL;}int main(){int server_len,remote_len;pthread_t tid[10];int err,sd;int i=0;//创建socketsd = socket(AF_INET,SOCK_STREAM,0);if(sd == -1){printf(\"create socket failure ,errno is %d\\n\",errno);return 1;}//设置ip地址和端口server_ip.sin_family = AF_INET;//IPv4server_ip.sin_port = htons(5678);//端口server_ip.sin_addr.s_addr = htonl(INADDR_ANY);//0,自动匹配memset(server_ip.sin_zero,0,8);//绑定服务器ip地址和端口到socketerr = bind(sd,(struct sockaddr *)(&server_ip),sizeof(struct sockaddr));if(err == -1){printf(\"bind error , errno is %d\\n\",errno);close(sd);return 1;}//设置服务器到最大链接数err = listen(sd,MAX_LISTEN);if(err == -1){printf(\"listen error ,errno is %d\\n\",errno);close(sd);return 1;}//获取客户端ip地址的长度remote_len = sizeof(struct sockaddr);while(1){//等待客户端到请求,如果请求来到,返回一个新到socket//服务器和客户端利用新的socket来通信ad[i] = accept(sd,(struct sockaddr *)(&remote_ip),&remote_len);if(ad[i] == -1){printf(\"accept error , errno is %d\\n\",errno);close(sd);return 1;}//抛出一个新的线程,在新线程中使用while循环,确保能不停的交换数据err = pthread_create(&tid[i],NULL,thread_fun,(void *)i);if(err != 0){printf(\"create new thread failure\\n\");close(ad[i]);}pthread_join(tid[i],NULL);i++;}close(sd);return 0;}

运行结果

read data from client:192.168.1.10buf is 1111122222read data from client:192.168.1.10buf is 11111read data from client:192.168.1.10buf is aaaaread data from client:192.168.1.10

网络调试助手界面,我的虚拟机IP为192.168.1.12,端口设置为5678

client

#include <pthread.h>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <errno.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>char buf[100];int sd;struct sockaddr_in server_ip;void *thread_write(void *arg){while(1){write(sd,\"hello\",6);sleep(1);}}void *thread_read(void *arg){while(1){sleep(1);meset(buf,0,100);read(sd,buf,100);printf(\"client 2 say:%s\\n\",buf);}}int main(){int server_len,remote_len;pthread_t tid_read,tid_write;int err;sd=socket(AF_INET, SOCK_STREAM, 0);if(sd==-1){printf(\"create socket failed, errno is %d\\n\",errno);return 1;}server_ip.sin_family = AF_INET;server_ip.sin_port = htons(5678);//端口server_ip.sin_addr.s_addr = htonl(INADDR_ANY);//0,自动匹配memset(server_ip.sin_zero,0,8);err=connect(sd,(struct sockaddr *)(&server_ip),sizeof(struct sockaddr));if(err==1){printf(\"connect error\\n\");close(sd);return 1;}err = pthread_create(&tid_read,NULL,thread_read,NULL);if(err != 0){printf(\"create new thread failure\\n\");close(sd);return 1;}err = pthread_create(&tid_write,NULL,thread_read,NULL);if(err != 0){printf(\"create new thread failure\\n\");close(sd);return 1;}pthread_join(tid_read,NULL);pthread_join(tid_write,NULL);close(sd);return 0;}
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 基于多线程的TCP并发服务器的创建及相关函数