TCP编程
对于TC编程的实现,我们首先需要掌握的是下图中的内容,对于编程时服务器和客户端分别需要实现的功能.
两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
对于其中的函数用法可以参考 LINUX-网络编程常用函数介绍篇
头文件
以下是本次编程过程中需要用到的头文件以及宏定义
net.h
#ifndef _NET_H
#define _NET_H
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<errno.h>
#include<pthread.h>
#include <ifaddrs.h>
#endif
common.h
#ifndef _COMMON_H
#define _COMMON_H
#define SERV_PORT 5001
#define SERV_IP_ADDR "127.0.0.1"//本地回环地址
#define BACKLOG 5//请求队列大小
#define QUIT_STR "quit"
#endif
实现一个简单的TCP网络编程
客户端(client.c)
#include"net.h"
#include"common.h"
int main(){
int fd = -1;
struct sockaddr_in sin;
/*创建socket fd*/
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
/*连接服务器*/
/*填充struct sockaddr_in结构体变量*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("connect");
exit(-1);
}
/*读写数据*/
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin) == NULL){
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("Client is exiting\n");
break;
}
}
/*关闭套接字*/
close(fd);
return 0;
}
服务器(server.c)
#include"common.h"
#include"net.h"
int main(void){
int fd = -1;
struct sockaddr_in sin;
/*创建socket fd*/
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
/*填充struct sockaddr_in结构体变量*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
/*绑定*/
if(bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("bind");
exit(-1);
}
/*调用listen()在bind()对应的地址和端口上监听*/
if(listen(fd,BACKLOG) == -1){
perror("listen");
exit(-1);
}
int newfd = -1;
/*阻塞等待客户端连接请求*/
newfd = accept(fd,NULL,NULL);
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 exiting\n");
break;
}
}
close(newfd);
close(fd);
return 0;
}
改进
优化1:服务器可查看网卡对于的IP地址,并自动绑定
#include <ifaddrs.h>//需添加头文件查询IP地址
//查询本机网卡对应IP地址
struct ifaddrs * ifAddrStruct=NULL;
void * tmpAddrPtr=NULL;
getifaddrs(&ifAddrStruct);
puts("请选择以下网卡对应IP地址进行连接服务器:");
while (ifAddrStruct!=NULL) {
if (ifAddrStruct->ifa_addr->sa_family==AF_INET) {
// check it is IP4
// is a valid IP4 Address
tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
printf("网卡:%-5s IPV4 地址:%s\n", ifAddrStruct->ifa_name, addressBuffer);
}
/*
else if (ifAddrStruct->ifa_addr->sa_family==AF_INET6) {
// check it is IP6
// is a valid IP6 Address
tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
char addressBuffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
printf("网卡:%-5s IPV6 地址:%s\n", ifAddrStruct->ifa_name, addressBuffer);
}
*/
ifAddrStruct=ifAddrStruct->ifa_next;
}
优化2:客户端适用于IPV4/IPV6的地址转换成网络二进制格式
#if 0
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr)!= 1){
perror("inet_pton");
exit(-1);
}
#endif
优化3:允许绑定地址快速重用
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
优化4:实现多线程并发服务器
/*阻塞等待客户端连接请求*/
pthread_t tid;
while(1){
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
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("客户端(%s:%d)加入!",ipv4_addr,htons(cin.sin_port));
pthread_create(&tid,NULL,(void *)cli_data_handle,(void *)&newfd);
}
close(fd);
return 0;
}
void cli_data_handle(void *arg){
int newfd = *(int *)arg;
printf("\t号码为%d\n",newfd);
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("客户端%d号发送: %s\n",newfd,buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("客户端%d号退出!\n",newfd);
break;
}
}
close(newfd);
}
优化一个简单的TCP网络编程
客户端(client1.c)
/*./client serv_ip serv_port*/
#include"net.h"
#include"common.h"
int main(int argc,char **argv){
int fd = -1;
int port = SERV_PORT;
struct sockaddr_in sin;
//输入参数判断
if(argc != 3){
printf("请输入: %s <serverip> <port>\n",argv[0]);
}
/*创建socket fd*/
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
if(atoi(argv[2]) < 5000 || atoi(argv[2]) > 65535){
printf("不正确的端口号%d,用缺省端口号5001\n",port);
}else{
port = atoi(argv[2]);
}
/*连接服务器*/
/*填充struct sockaddr_in结构体变量*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr)!= 1){
perror("inet_pton");
exit(-1);
}
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("connect");
exit(-1);
}
printf("连接成功!\n");
/*读写数据*/
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
printf("请输入 > ");
if(fgets(buf,BUFSIZ-1,stdin) == NULL){
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("客户端退出!\n");
break;
}
}
/*关闭套接字*/
close(fd);
return 0;
}
服务器(server1.c)
#include"net.h"
#include"common.h"
void cli_data_handle(void *arg);
int main(void){
int fd = -1;
struct sockaddr_in sin;
struct ifaddrs * ifAddrStruct=NULL;
void * tmpAddrPtr=NULL;
//查询本机网卡对应IP地址
getifaddrs(&ifAddrStruct);
puts("请选择以下网卡对应IP地址进行连接服务器:");
while (ifAddrStruct!=NULL) {
if (ifAddrStruct->ifa_addr->sa_family==AF_INET) {
// check it is IP4
// is a valid IP4 Address
tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
printf("网卡:%-5s IPV4 地址:%s\n", ifAddrStruct->ifa_name, addressBuffer);
}
/*
else if (ifAddrStruct->ifa_addr->sa_family==AF_INET6) {
// check it is IP6
// is a valid IP6 Address
tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
char addressBuffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
printf("网卡:%-5s IPV6 地址:%s\n", ifAddrStruct->ifa_name, addressBuffer);
}
*/
ifAddrStruct=ifAddrStruct->ifa_next;
}
/*创建socket fd*/
if((fd = socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
/*允许绑定地址快速重用*/
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
/*填充struct sockaddr_in结构体变量*/
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
/*绑定*/
if(bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
perror("bind");
exit(-1);
}
/*调用listen()在bind()对应的地址和端口上监听*/
if(listen(fd,BACKLOG) == -1){
perror("listen");
exit(-1);
}
printf("等待连接!\n");
int newfd = -1;
/*阻塞等待客户端连接请求*/
pthread_t tid;
while(1){
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
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("客户端(%s:%d)加入!",ipv4_addr,htons(cin.sin_port));
pthread_create(&tid,NULL,(void *)cli_data_handle,(void *)&newfd);
}
close(fd);
return 0;
}
void cli_data_handle(void *arg){
int newfd = *(int *)arg;
printf("\t号码为%d\n",newfd);
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("客户端%d号发送: %s\n",newfd,buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf("客户端%d号退出!\n",newfd);
break;
}
}
close(newfd);
}