一、 tcp服务器(并发)
-
循环服务器
-
关心客户端的ip和端口
-
设置地址重用(端口没有真正被某一进程 占用)
-
拓展: 条件编译和 宏函数的调试
条件编译
// 宏函数实现 开关打印 调试
//#define QDEBUG //这个QDEBUG 宏存在 则 开启 否则 关闭
#ifdef QDEBUG
#define pri(fmt,...); printf("%s\n",fmt);
#else
#define pri(fmt, ...);
#endif
- setsockopt函数:
setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区长度。
optname:需设置的选项:
SO_BINDTODEV char * 将套接字绑定到指定端口。
SO_BROADCAST BOOL 允许套接口传送广播信息。
SO_DEBUG BOOL 记录调试信息。
SO_DONTLINER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。
SO_DONTROUTE BOOL 禁止选径;直接传送。
SO_KEEPALIVE BOOL 发送“保持活动”包。
SO_LINGER struct linger FAR* 如关闭时有未发送数据,则逗留。
SO_OOBINLINE BOOL 在常规数据流中接收带外数据。
SO_RCVBUF int 为接收确定缓冲区大小。
SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
SO_SNDBUF int 指定发送缓冲区大小。
TCP_NODELAY BOOL 禁止发送合并的Nagle算法。
setsockopt()不支持的BSD选项有:
选项名 类型 意义
SO_ACCEPTCONN BOOL 套接口在监听。
SO_ERROR int 获取错误状态并清除。
SO_RCVLOWAT int 接收低级水印。
SO_RCVTIMEO int 接收超时。
SO_SNDLOWAT int 发送低级水印。
SO_SNDTIMEO int 发送超时。
SO_TYPE int 套接口类型。
IP_OPTIONS 在IP头中设置选项。
重点:
SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind())。
SO_BROADCAST BOOL 允许套接口传送广播信息。
SO_RCVTIMEO int 接收超时。
SO_SNDTIMEO int 发送超时。
返回值:
若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
错误代码:
WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
WSAENETDOWN:套接口实现检测到网络子系统失效。
WSAEFAULT:optval不是进程地址空间中的一个有效部分。
WSAEINPROGRESS:一个阻塞的套接口调用正在运行中。
WSAEINVAL:level值非法,或optval中的信息非法。
WSAENETRESET:当SO_KEEPALIVE设置后连接超时。
WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。
WSAENOTSOCK:描述字不是一个套接口。
二.TCP 客户端实现
-
搭建流程:
- socket
- 准备 要连接的服务器ip 和端口等
- connect 连接服务器
- 通信
- 关闭套接字
- socket
-
connect 函数
功能:connect - connect a socket
头文件:SYNOPSIS
#include <sys/socket.h>
函数原型:
int connect(int socket, const struct sockaddr *address,
socklen_t address_len);
参数说明:
返回值:
三、实现并发
1.多进程的服务器
-
进程的相关函数
- fork()
- 回收:
- wait()
- waitpid( -1 ,NULL , WNOHANG)
- signal()
//让父进程 负责监听 子进程 负责通信
signal( SIGCHLD , signal_handerl); //注册信号
socket ()
bind
listen
while(1)
{
connfd = accept()
{
fork();
pid > 0
{
close(connfd);
//break; //退出循环 等待监听
}
pid == 0
{
close(listenfd);
//通信
}
}
}
//子进程回收机制
int signal_handerl(int sig)
{
waitpid();
}
多进程服务器代码
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h> /*See NOTES */
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
//宏函数实现条件编译
#ifndef QDEBUG
#define pri(fmt,...); printf("%s\n",fmt);
#else
#define pri(frt,...);
#endif
#define SERV_IP "0.0.0.0"
#define SERV_PORT 8888
void signal_recycle(int sig);//子进程回收函数
int serv_init();//监听初始化
void do_communication(int connfd);//通信
int main(int argc, char *argv[])
{
//1.创建监听套接字
//2.绑定服务器ip和端口
//3.监听到连接请求建立连接
//4.while循环中创建进程,主进程实现监听功能,每一个连接创建一个子进程实现通信
//注册SIGCHLD信号,回收子进程
signal(SIGCHLD,signal_recycle);
int listenfd ;
int connfd ;
listenfd = serv_init();//监听
if( -1 == listenfd )
{
perror("serv_init fail");
return -1;
}
while(1)
{
//建立连接
connfd = accept(listenfd,NULL,NULL);
if( -1 == connfd )
{
if( errno == EINTR )//被信号打断则不结束,再来一次
{
continue;
}
else
{
exit(0);
}
}
//创建子进程
pid_t pid ;
flag: pid = fork();
if( -1 == pid )//进程创建失败
{
goto flag;//重新创建
}
else if( 0 == pid )//子进程进行通信
{
close(listenfd);//子进程关闭监听,只进行通信
do_communication( connfd);
}
else
{
//父进程只监听,不通信
close(connfd);
continue;
}
}
return 0;
}
void signal_recycle(int sig)//子进程回收函数
{
while(waitpid(-1,NULL,WNOHANG)>0);
}
int serv_init()//监听初始化
{
int listenfd ;
int ret = -1 ;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if( -1 == listenfd)
{
perror("socket fail");
exit(-1);
}
pri("socket listenfd OK");
//准备ip和端口
struct sockaddr_in ser_addr ={
.sin_family = AF_INET,
.sin_port = htons(SERV_PORT),
.sin_addr.s_addr = inet_addr(SERV_IP)
};
//设置地址重用
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//SO_REUSEADDR 允许在bind()过程中本地地址可重复使用
//绑定
ret = bind(listenfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if( -1 == ret )
{
perror("bind ");
exit(-1);
}
pri("bind OK");
//监听
ret = listen(listenfd,1024);
if( -1 == ret )
{
perror("listen fail");
exit(-1);
}
pri("listen OK");
return listenfd ;
}
void do_communication(int connfd)
{
char buf[BUFSIZ];
while(1)
{
memset(buf ,0 ,BUFSIZ);
int ret = read(connfd,buf,BUFSIZ);
if( 0 == ret )
{
puts(" quit ");
close(connfd);//关闭连接
exit(0);
}
else if( -1 == ret )
{
perror("read fail");
close(connfd);
exit(-1);
}
else
{
puts(buf);
}
}
}
2.多线程服务器
-
线程函数
- pthread_create(&tid, NULL, thread_fun, (void *)arg);
int arg = 4;
一、
pthread_create(&tid, NULL, thread_fun, (void *)arg);
void *thread_fun(void arg)
{
int connfd = (int ) arg;
}
二、
pthread_create(&tid, NULL, thread_fun, (void *)&arg);
void *thread_fun(void *arg)
{
int connfd = *(int *) arg;
}
-
pthread_exit()
-
pthread_join(); //阻塞
-
pthread_detach(); // 线程分离
-
框架:
//主线程 实现监听
//子线程 实现通信
server_init()
while(1)
{
connfd = accept();
pthread_create(); //监听
pthread_create() ; //通信
}
void * thread_fun(void *arg)
{
int connfd = (int )arg;
}
多线程代码
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h> /*See NOTES */
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
//宏函数实现条件编译
#ifndef QDEBUG
#define pri(fmt,...); printf("%s\n",fmt);
#else
#define pri(frt,...);
#endif
#define SERV_IP "0.0.0.0"
#define SERV_PORT 8888
int serv_init();//监听初始化
void* do_communication(void *fd);//通信
int main(int argc, char *argv[])
{
//1.创建监听套接字
//2.绑定服务器ip和端口
//3.监听到连接请求建立连接
//4.while循环中创建线程,主线程实现监听功能,每一个连接创建一个子线程实现通信
int listenfd ;
int fd ;
listenfd = serv_init();//监听
if( -1 == listenfd )
{
perror("serv_init fail");
return -1;
}
while(1)
{
//建立连接
fd = accept(listenfd,NULL,NULL);
if( -1 == fd )
{
if( errno == EINTR )//被信号打断则不结束,再来一次
{
continue;
}
else
{
exit(0);
}
}
//创建子线程
pthread_t tid ;
pthread_create(&tid,NULL,do_communication,&fd);//子线程实现通信号
pthread_detach(tid);//线程分离
}
return 0;
}
int serv_init()//监听初始化
{
int listenfd ;
int ret = -1 ;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if( -1 == listenfd)
{
perror("socket fail");
exit(-1);
}
pri("socket listenfd OK");
//准备ip和端口
struct sockaddr_in ser_addr ={
.sin_family = AF_INET,
.sin_port = htons(SERV_PORT),
.sin_addr.s_addr = inet_addr(SERV_IP)
};
//设置地址重用
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//SO_REUSEADDR 允许在bind()过程中本地地址可重复使用
//绑定
ret = bind(listenfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if( -1 == ret )
{
perror("bind ");
exit(-1);
}
pri("bind OK");
//监听
ret = listen(listenfd,1024);
if( -1 == ret )
{
perror("listen fail");
exit(-1);
}
pri("listen OK");
return listenfd ;
}
void * do_communication(void * fd)
{
char buf[BUFSIZ];
int connfd = *(int*)fd;
while(1)
{
memset(buf ,0 ,BUFSIZ);
int ret = read(connfd,buf,BUFSIZ);
if( 0 == ret )
{
puts(" quit ");
close(connfd);//关闭连接
return NULL;
}
else if( -1 == ret )
{
perror("read fail");
close(connfd);
return NULL ;
}
else
{
puts(buf);
}
}
}
4.多进程和多线程的服务器区别:
1、都是并发、用文件描述符标识 客户端的通信(3-1023)
2、进程是申请资源的最小单位
3、线程是执行的最小单位