基于多线程的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_INETIPv4的因特网域
AF_INET6IPv6的的因特网域
AF_UNIXUNIX域
AF_UNSPEC未指定

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

类型描述
SOCK_DGRAM长度固定的,无连接的不可靠报文传输
SOCK_RAWIP协议的数据报接口
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 10
char 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;
        //创建socket
        sd = 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;//IPv4
        server_ip.sin_port = htons(5678);//端口
        server_ip.sin_addr.s_addr = htonl(INADDR_ANY);//0,自动匹配
        memset(server_ip.sin_zero,0,8);
 
        //绑定服务器ip地址和端口到socket
        err = 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.10
buf is 1111122222
read data from client:192.168.1.10
buf is 11111
read data from client:192.168.1.10
buf is aaaa
read 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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值