函数详解
https://blog.csdn.net/seraphsky/article/details/1856191
https://www.jianshu.com/p/066d99da7cbd
可能需要的头文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
宏定义
#define SERV_PORT 5001 必须>=5000
#define SERV_IP_ADDR “192.168.7.246”
#define BACKLOG 5
#define QUIT_STR “quit”
tcp框体之client
int main(int argv,const char * argv[])
{
int fd = -1;
struct sockaddr_in sin;
/*
优化:判断是否传入文件名、IP、端口号
if(argv != 3)
{
usage(argv[0]);
exit(0);
}
usage(char *s){
printf("\n%s serv_ip serv_port\n",s);
printf("\tserv_ip:server ip address");
printf("\tserv_port"server port(>5000)");
}
*/
//1、建立套接字
if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket");
exit(1);
}
/*
AF_INET ------>IPV4
AF_INET6 ----->IPV6
SOCK_STREAM--->流式套接字,用于密码一类,可靠,效率低,用于TCP
SOCK_DGRAM---->数据报套接字,用于发送消息等,不可靠,效率高,用于UDP
*/
//2、填充消息结构体
bzero(&sin,sizeof(sin));
sin.sin_faily = AF_INET;
sin.sin_port = htons(SERV_PORT); //本地字节序转网络字节序
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); //将点分形式ip地址转换为2进制32位地址。
/*优化方案之端口号port:
**改为由用户输入的端口号:**
int port = -1;
port = atoi(argv[2]);//将字符串转化为整形数,若为字母转化结果为0
if(port < 5000)
{
printf("server port( > 5000)\n");
exit(1);
}
sin.sin_port = htons(port);
优化方案之ip:
if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr) != 1)
//将ipv4/ipv6转换为2进制数并存入sin.sin_addr.s_addr
{
perror("inet_pton");
exit(1);
}
*/
//3、连接
if(connet(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("connect");
exit(1);
}
//4、无限循环中开始办事
//4.1client最基础框架
char buf[BUFSIZ];
int ret = -1;
while(1)
{
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin) == NULL)
//从键盘输入字符串存入缓存区buf中,若不成功继续执行
{
continue;
}
do{
ret = write(fd,buf,strlen(buf));
//将缓存区buf中数据循环写入套接字fd中
}while(ret < 0 && EINTR == errno);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("CLIENT EXTIING\N");
break; //跳出循环
}
}
//4.2client端多进程并发
同4.1
//4.3client端多线程并发
同4.1
//4.4client端http
#define HTTP_RCV_LEN 50000
const char getstr[] = "GET /index.html HTTP/1.1\r\n"
"Host:www.baidu.com\r\n"
"\r\n";
struct hostent *phostent;
char *http_msg;
char *ipaddr = NULL;
if((phostent = gethostbyname("www.baidu.com")) == NULL)
{
perror("gethostbyname");
exit(1);
}else{
for(i=0;phostent->h_addr_list[i];i++)
{
ipaddr = inet_ntoa(*(struct in_addr*)(phostent->h_addr_list[i]));
if(ipaddr != NULL)
break;
}
if(ipaddr == NULL)
{
exit(1);
}
}
http_msg = zalloc(HTTP_RCV_LEN);
send(fd,getstr,strlen(getstr),0);
int ret = -1;
while(1)
{
do
{
ret = recv(fd,http_msg,50000,0);
if(ret > 0)
{
printf("%s",http_msg);
}else{
printf(" no http server data");
}
}(ret = -1);
}
free(htttp_msg);
//4.5io多路复用client端:用于处理阻塞问题
具体方法为先构造一张描述符集合的表,将多个文件描述符加入其中,调用函数,函数返回值表示就绪。
int ret = -1;
fd_set rset; //文件描述符集合名称
int maxfd = -1; //文件描述符个数
struct timeval tout; //超时设置。NULL:阻塞至就绪或出错;0:仅检测状态,立即返回;!=0:指定时间内无事发生便返回/
char buf[BUFSIZ];
while(1)
{
FD_ZERO(&rset); //清空集合
FD_SET(0,&rset); //加入标准输入流
FD_SET(fd,&rset); //加入套接字fd
maxfd = fd; //一个时候为3,因为有标准输入输出报错012
tout.tv_sec = 5; //秒
tou.tv_usec = 0; //微妙
select(maxfd+1,&rset,NULL,NULL,&tout);
if(FD_ISSET(0,&rset)) //若是标准键盘输入
{
bzero(buf,BUFSIZ);
do{
ret = read(0,buf,BUFSIZ -1);
}while(ret < 0 && EINTR == errno);
if(!ret)
continue;
if(ret < 0)
{
perror("read");
continue;
}
if(write(fd,buf,strlen(buf)) < 0){
perror("write");
continue;
}
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exiting\n")
break;
}
}
if(FD_ISSET(fd,&rset)) //若是客户端发的数据
{
bzero(buf,BUFSIZ);
do{
ret = read(fd,buf,BUFSIZ-1);
}while(ret < 0 && EINTR == errno);
if(ret < 0){
perror("read");
continue;
}
if(!ret)
break;
printf("server said:%s\n",buf);
}
}
//5、关闭套接字
close(fd);
}
tcp框体之server
int main(int argc,const char *argv[])
{
/*优化:避免阻塞
signal(SIGCHLD,sig_child_handle);//非阻塞等待子进程结束回收,SIGCHLD:子进程状态发生变化产生该信号
void sig_child_handle(int signo)
{
if(signo == SIGCHLD){
waitpit(-1,NULL,WNOHANG);
}
}
*/
//1、建立套接字
if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket");
exit(1);
}
/*优化:允许绑定地址快速重用
int b_reause = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
*/设置与套接字相关选项的函数,总会出现在最上层套接字层
//2、填充sin消息结构
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);//让服务器可绑定任意ip
/*sin.sin_addr.s_addr的另一种表示
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr) != 1)
{
perror("inet_pton");
exit(1);
}
*/
//3、将本地消息结构与套接字建立联系
if(bind(fd,(struct sockaddr *)&sin.sizeof(sin)) < 0)
{
perror("bind");
exit(1);
}
//4、主动套接字变被动套接字
if(listen(fd,BACKLOG) < 0)
//BACKLOG:请求队列长度,表示能存放多少个客户端。
{
perror("listen");
exit(1);
}
//5、连接并在无限循环中处理数据
//5.1server最基础框架
int newfd;
newfd = accept(fd,NULL,NULL);//继承fd
if(newfd < 0){
perror("accept");
exit(1);
}
int ret = -1;
char buf[BUFSIZ];
while(1)
{
bzero(buf,BUFSIZ);
do{
ret = read(newfd,buf,BUFSIZ-1);
}while(ret < 0&& EINTR == errno);
if(ret < 0){
perror("read");
exit(1);
}
if(!ret){ //表示对方已经关闭,则直接退出循环
break;
}
printf("receive data :%s\n",buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exting \n");
break;
}
}
clise(newfd); //推出循环后关闭newfd
//5.2server端多进程并发
int newfd = -1;
struct sockaddr_in cin;//存放客户端消息结构
socklen_t addrlen = sizeof(cin);
while(1){
pid_t pid = -1;
if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)//继承fd,在cin中存放客户端信息,个人理解为改名
{
perror("accept");
exit(1);
}
//创建一个子进程用于处理已经建立连接的客户的交互数据
if((pid = fork()) < 0)
{
perror("fork");
break;
}
if(pid > 0) //父进程关闭newfd
{
close(newfd);
}
if(pid == 0) //子进程关闭fd
{
clise(fd);
char ipv4_addr[16];//显示接收到的客户端的IP&端口号
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) //32位地址转化为点分形式ip存入ipv4_addr缓存区
{
perror("inet_ntop");
exit(1)
}
printf("client(%s,%d)is connected\n",ipv4_addr,ntoh(cin.sin_port));
**cli_data_handle(&newfd);**//具体操作函数
return 0;
}
}
//5.3server端多线程并发
while(1)
{
if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
{
perror("inet_ntop");
exit(1);
}
printf("client(%s:%d) is connected\n",ipv4_addr,htons(cin.sin_port));
**pthread_create(&tid,NULL,(void *)cli_data_handle,(void *)&newfd);**
}
/*客户端数据处理函数
void cli_data_handle(void *arg)
{
int newfd = *(int *)arg;
printf("child process:newfd = %d\n",newfd);
/*同5.1 while(1)*/
close(newfd);
}
*/
//5.4server端http
int newfd = -1;
int ret = -1;
struct sockaddr cin;
socklen_t cin_len = sizeof(struct sockaddr);
char *http_msg;
char *send_msg;
http_msg = (char *)zalloc(sizeof(char)*1000);
const char *default_page =
"<html>"
"<head>"
"<meta http-equiv=\"Content-Type\"content=\"text/html;charset = utf-8\"/>"
"<title></title>"
"/head"
"<body>"
"<p>1234556</p>"
"</body>"
"</thml>"
while(1)
{
if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)
{
perror("accept");
exit(1);
}
ret = recv(newfd,http_msg,1000,0);
if(ret > 0)
{
printf("http_client recv\n");
printf("%s\n",http_msg);
**file_ok(newfd,strlen(default_page));**
send(newfd,default_page,strlen(default_page), 0 );
}else{
printf("no data\0");
}
}
int file_ok(int newfd , long flen)
{
char *send_buf = zalloc(sizeof(char)*100);
sprintf(send_buf,"HTTP/1.1 200 OK\r\n");
send(newfd,send_buf,strlen(send_buf),0);
sprintf(send_buf,"Connection:keeo-alive\r\n");
send(newfd,send_buf,strlen(send_buf),0);
sprintf(send_buf,"Content-Length:%ld\r\n",flen);
send(newfd,send_buf,strlen(send_buf),0);
sprintf(send_buf,"Content-Type:text/html\r\n");
send(newfd,send_buf,strlen(send_buf),0);
sprintf(send_buf,"\r\n");
send(newfd,send_buf,strlen(send_buf),0);
free(send_buf);
return 0;
}
//5.5 io多路复用server端:没有变化,正常处理数据
同5.2
//6、关闭套接字
close(fd);
return 0;
}