启动服务器:
bin/echo_tcp_server_th 8888
启动客户端:
bin/echo_tcp_client 127.0.0.1 8888
使用ifconfig命令可以看到电脑的换回地址是127.0.0.1 因为服务器和客户端在一台电脑上使用换回地址进行测试;
lo Link encap:本地环回
inet 地址:127.0.0.1 掩码:255.0.0.0
inet6 地址: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 跃点数:1
接收数据包:2371 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:2371 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:287712 (287.7 KB) 发送字节:287712 (287.7 KB)
文件树:
.
├── bin
│ ├── echo_tcp_client
│ ├── echo_tcp_server
│ ├── echo_tcp_server_th
│ ├── time_tcp_client
│ └── time_tcp_server
├── include
│ └── msg.h
├── Makefile
├── obj
│ └── msg.o
├── src
│ ├── echo_tcp_client.c
│ ├── echo_tcp_server.c
│ ├── echo_tcp_server_th.c
│ ├── msg.c
│ ├── time_tcp_client.c
│ └── time_tcp_server.c
└── time_tcp_demo.tar.gz
话不多说上服务器代码:
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>
#include "msg.h"
#include <sys/signal.h>
#include <errno.h>
#include <sys/types.h>
#include <wait.h>
#include <arpa/inet.h>
#include <pthread.h>
/**************************************使用多进程处理并发***************************************/
//处理多个进程连接
//测试方法,运行程序,并指定端口号8888
//在另一个终端上使用
// telnet 127.0.0.1 8888进行连接
//127.0.0.1是没有连接网络时使用的本地回环ip地址
/*声明自定义函数*/
void sig_handler(int signo);
void do_service(int fd);
void *th_fn(void *arg);
void out_fd(int fd);
int sockfd;
int main(int argc, char * argv[])
{
if(argc < 2)
{
printf("usage: %s #port\n",argv[0]);
exit(1);
}
if(signal(SIGINT, sig_handler) == SIG_ERR) //开始捕捉信号 SIGINT
{
perror("signal sigint error!");
exit(1);
}
/*步骤1创建socket
*AF_INET IPV4
*SOCK_STREAM 采用tcp协议
*0 采用上面设置的协议类型
*/
sockfd = socket(AF_INET, SOCK_STREAM, 0); //使用默认协议
if(sockfd < 0)
{
perror("socket error!");
exit(1);
}
/*
*步骤2,:调用bind函数将socket和地址(包括 IP,port)进行绑定
**/
struct sockaddr_in serveraddr; //声明专用地址,需要的时候再转换为通用地址
memset(&serveraddr, 0, sizeof(serveraddr));
//往地址填入ip、port、intnernet地址族类型
serveraddr.sin_family = AF_INET; //IPV4
serveraddr.sin_port = htons(atoi(argv[1])); //填入端口
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd, (struct sockaddr *) &serveraddr,sizeof(serveraddr)) < 0)
{
perror("bind error!");
exit(1);
}
/*
*步骤3:调用listen函数启动端口监听
*通知系统去接受来自客户端的连接请求
*listen 的第二个参数10是请求队列长度,将接收到的客户端连接请求放到对应的队列当中
*/
if(listen(sockfd, 10) < 0) //10监听的队列的上限
{
perror("listen error!");
exit(1);
}
/*
*步骤4:调用accept函数,从队列中获得
* 一个客户端请求连接,并返回新的sock文件描述符fd,
* 因为listen能够监听好多的连接请求,
* 使用accept获得一个连接使用
* 若是accept没有获得客户端连接,会进行阻塞,直到获得客户端连接
*/
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
//使用分离状态的子线程, 需要设置子线程的分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
//设置分离属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
while(1)
{
//使用循环就能够在断开与一个客户端连接之后,在连接下一个客户端连接
//调用accept就是在客户端获得一个连接
//主控线程负责调用accept函数获得客户端的连接
int fd = accept(sockfd, NULL, NULL);
if(fd < 0)
{
perror("accept error!");
continue;
}
/*
*步骤5: 启动子线程调用IO函数(read/write)和连接的客户端进行双向通信
*/
//以分离状态启动的子线程在结束的时候会自动的释放自己占用的资源,不用主线程在使用join函数
pthread_t th;
int err;
//将套接字的文件描述符传进线程创建函数
if((err = pthread_create(&th, &attr, th_fn, (void *)fd)) != 0)
{
perror("pthread create error!");
}
pthread_attr_destroy(&attr);
}
return 0;
}
void sig_handler(int signo)
{
if(signo == SIGINT)
{
printf("server close\n");
/*步骤6:关闭socket*/
close(sockfd);
exit(1);
}
/* //登记SIGCHLD信号 只有子进程结束才会出现,使用线程时去掉
if(signo == SIGCHLD)
{
printf("child process deaded...\n");
wait(NULL); //会自动回收子进程 wait函数中填入0说明对于回收的子进程不关心只是去回收子进程
}
*/
}
void do_service(int fd)
{
/*和客户端双向通讯,进行读写操作*/
char buff[512];
while(1)
{
memset(buff, 0, sizeof(buff));
size_t size;
if((size = read_msg(fd, buff, sizeof(buff))) < 0)
{
perror("protocal error!");
break;
}
else if (size == 0)
{//数据全部读取完毕或者对方客户端挂掉就会返回0
break;
}
else
{
printf("%s\n", buff);
if(write_msg(fd, buff, sizeof(buff)) < 0)
{
if(errno == EPIPE)//若是对方已经关闭就直接跳出
{
perror("write_msg error!");
break;
}
}
}
}
}
void *th_fn(void *arg)
{
int fd = (int)arg;
do_service(fd);
out_fd(fd); //服务完毕之后输出客户端的一些信息
close(fd);
return (void *)0;
}
void out_fd(int fd)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
//从fd中获得连接的客户端的相关信息,把信息放进sockaddr_in的结构体addr中
if(getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
{
perror("getpeername error!");
return ;
}
char ip[16];
memset(ip, 0, sizeof(ip));
int port = ntohs(addr.sin_port);
inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));
printf("%16s(%5d) closed!\n", ip, port);
}
客户端代码:
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>
#include "msg.h"
#include <sys/signal.h>
#include <errno.h>
#include <sys/types.h>
#include <wait.h>
#include <arpa/inet.h>
int main(int argc, char * argv[])
{
if(argc < 3)
{
printf("usage:%s ip port\n",argv[0]);
exit(1);
}
/*步骤1:创建socket*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error!");
exit(1);
}
/*往serveraddr中填入ip,port和地址族类型(ipv4)*/
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
/*将字符串ip地址转换为网络字节序填入 serveraddr中*/
inet_pton(AF_INET, argv[1],
&serveraddr.sin_addr.s_addr);
/*
*步骤2: 客户端调用connect函数连接到服务器端
*/
if(connect(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0)
{
perror("connect error!");
exit(1);
}
/*步骤3:调用IO函数,read和write和服务器进行双向通信*/
char buff[512];
size_t size;
char *prompt = ">";
while(1)
{
memset(buff, 0, sizeof(buff));
write(STDOUT_FILENO, prompt, 1);
size = read(STDIN_FILENO, buff, sizeof(buff));
if(size < 0) continue;
buff[size - 1] = '\0';
if(write_msg(sockfd, buff, sizeof(buff)) < 0)
{
perror("write error!");
continue; //就算写出错也结合运行 进行下次的发送
}
else
{
//客户端发送成功
if(read_msg(sockfd, buff, sizeof(buff)) < 0)
{
perror("read msg error!");
continue;
}
else
{
printf("%s\n",buff);
}
}
}
/*步骤4:关闭socket套接字*/
close(sockfd);
return 0;
}
Makefile文件;
.PHONY : all
.PHONY : clean
all :bin/echo_tcp_server_th bin/echo_tcp_client
bin/echo_tcp_server_th : echo_tcp_server_th
mv echo_tcp_server_th bin/echo_tcp_server_th
bin/echo_tcp_client : echo_tcp_client
mv echo_tcp_client bin/echo_tcp_client
echo_tcp_server_th : src/echo_tcp_server_th.c obj/msg.o
gcc -o echo_tcp_server_th -Iinclude src/echo_tcp_server_th.c obj/msg.o -lpthread
echo_tcp_client : src/echo_tcp_client.c obj/msg.o
gcc -o echo_tcp_client -Iinclude src/echo_tcp_client.c obj/msg.o
obj/msg.o : msg.o
mv msg.o obj/msg.o
msg.o : src/msg.c include/msg.h
gcc -c -Iinclude src/msg.c
clean:
-rm bin/* obj/*