Linux 网络编程之TCP(上)

来源:互联网 发布:数据有效性设置日期 编辑:程序博客网 时间:2024/05/29 09:14

转自 http://blog.csdn.net/chenjin_zhong/article/details/7253782

1.TCP通信流程

TCP网络编程主要是用套接字来实现网络服务器与客户端通信,一个标准的套接字是由协议族,IP地址与端口号组成,它是TCP服务器与客户端通信的基础,

TCP的服务器端流程:

(1)建立套接字(socket)

(2)将地址结构绑定到套接字上(bind)

(3)监听本地端口,设置监听队列的长度,服务器将不能同时处理多个请求,将不能处理的请求放入等待队列中(listen)

(4)接受客户端的请求,返回客户端套接字描述符,服务器与客户端利用这个新的描述符进行传输数据(accept)

(5)数据传输(read,write)

(6)关闭套接字(close)

TCP客户端流程:

(1)建立套接字(socket)

(2)连接目标服务器(connect)

(3)数据传输(read,write)

(4)关闭客户端套接字(close)

这样,就可以实现服务器与客户端通信了.

2. 相关数据结构

(1)通用套接字地址类型 

struct sockaddr{

sa_family_t sa_family;//协议族

char data[14];//协议数据

}

typedef unsigned short sa_family

以太网中常用的地址结构:

struct sockaddr_in{

 u8 sin_len;//struct sockaddr_in的长度

u8 sin_family;//协议族

u16 sin_port;//端口号

struct in_addr sin_addr;//32位IP地址

char sin_zero[8];//没使用

}

struct in_addr{

u32 s_addr;//32位地址

};

(2)相关函数

#include <sys/socket.h>

#include <sys/types.h>

int socket(int domain,int type,int protocol);

参数: 

domain-通信的域,AF_INET表示IPV4协议,PF_INET6表示IPV6协议

type-设置套接字通信的类型 SOCK_STREAM流式套接字,SOCK_DGRAM数据报套接字, SOCK_RAW原始套接字

protocol-表示某个通信类型的特定类型,如果type只有一个类型,那么这个参数设置为0

返回值:

成功返回套接字描述符,失败返回-1

#include <sys/types.h>

#include<sys/socket.h>

int bind(int sockfd,const struct sockaddr*my_addr,socket_t addrlen);

参数:

sockfd-套接字描述符

my_addr-sockaddr指针类型,指向一个套接字的地址结构

addrlen-地址结构的长度

返回值:

成功绑定返回0,失败绑定返回-1

#include <sys/socket.h>

int listen(int sockfd,int backlog);

参数:

sockfd-套接字描述符

backlog-等待队列的长度

返回值:

成功返回0,失败返回-1

#include <sys/types.h>

#include <sys/socket.h>

int accept(int sockfd,struct sockaddr*addr,socklen_t *addrlen);

参数:

sockfd-套接字描述符

addr-指向地址结构的指针

addrlen-地址结构的长度指针

其中addr包含的是客户端的地址结构,包括客户端的IP,端口号,协议族等等.

返回值:

成功返回的是用于服务器与客户端通信的套接字描述符,失败返回-1

#include <sys/types.h>

#include <sys/socket.h>

int connect(int sockfd,struct sockaddr*addr,int addrlen);

参数:

sockfd-客户端套接字描述符

addr-需要连接的服务器的地址结构

addrlen-服务器的地址结构的长度

返回值:

连接成功返回0,失败返回-1

#include <sys/socket.h>

close(int s);

参数:

s-套接字描述符

返回值:

成功关闭套接字返回0,失败返回-1


3. TCP通信的实例

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#define PORT 8888 
#define BACKLOG 2
//服务器端程序
/**
(1)建立一个套接字描述符,定义网络类型,协议类型和具体的协议标号 socket()
(2)将套接字与具体的地址结构绑定,这个地址结构包括端口号,IP地址,网络类型 bind()
(3)侦听客户端发出的请求,设置队列的长度 listen()
(4)在客户端发出请求后,处理客户端的连接,返回一个新的套接字描述符用于服务器端与客户端的通信 accept()
(5)读写数据 read() vs. write()
(6)当服务器处理完数据要结构通信过程,关闭连接 close()
**/
void process(int sc);
int main(int argc,char* argv[]){
 int ss,sc;//ss为服务器端的套接字描述符,sc为客户端的套接字描述符,即返回的新的套接字描述符
 struct sockaddr_in server_addr; //服务器端的地址结构
 struct sockaddr_in client_addr;//客户端的地址结构
 int err;//返回值
 pid_t pid;//进程的pid
 //建立一个流式套接字
 ss=socket(AF_INET,SOCK_STREAM,0);//第一个参数用来设置通信的域,AF_INET表示使用IPv4 Internet协议,第二个参数是套接字通信的类型,表示流式套接字,第三个参数表示某种协议的特定类型,如果只有一种类型,设置为0
 if(ss<0) {
    printf("socket error\n");
    return -1;

}
//设置服务器的地址结构
bzero(&server_addr,sizeof(server_addr));//清0
server_addr.sin_family=AF_INET;//通信的域,通常与socket函数的domain一致
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//本地地址
server_addr.sin_port=htons(PORT);//服务器端口
//绑定地址结构到套接字描述符
err=bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));//第一个参数是套接字描述符,第二个参数是sockaddr指针,第三个参数是server_addr的长度
if(err<0){
 perror("bind error");
 return -1;
}

//设置侦听
err=listen(ss,BACKLOG);//第一个参数是服务器端的套接字描述符,第二个参数是侦听队列的长度
if(err<0){
 perror("listen error");
  return -1;
}

for(;;){
 int addrlen=sizeof(struct sockaddr);
//接收客户端的连接
 sc=accept(ss,(struct sockaddr*)&client_addr,&addrlen);//第一个参数是服务器端的套接字描述符,第二个参数是sockaddr结构体指针,第三个参数是sockaddr长度的指针

if(sc<0){
  continue;
}
 //客户端的信息存储在sockaddr结构体
 int sin_port=client_addr.sin_port;//返回连接的客户端端口号
 printf("port is %d\n",sin_port);
//建立一个子进程来处理连接
pid=fork();
if(pid==0){//子进程
  close(ss);//关闭服务器的侦听
  process(sc);//处理连接,用返回的新的客户端描述符来处理连接

}else{
 close(sc);//在父进程关闭客户端的连接
}

}

}
void process(int sc){
  ssize_t size=0;
  char buffer[1024];
  for(;;){
    size=read(sc,buffer,1024);//返回读取的字节数
    if(size==0)
    return;
    write(1,buffer,size);//终端输出
    sprintf(buffer,"%d bytes\n",size);
    write(sc,buffer,strlen(buffer)+1);//发送给客户端    
  }


}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#define PORT 8888
/**
(1)建立套接字描述符
(2)connect与服务器端三次握手,连接服务器端
(3)read vs write
(4)关闭连接
**/
void process(int s);
int main(int argc,char* argv[]){
 int s;
 struct sockaddr_in server_addr;//服务器端的地址结构
 int err;
 s=socket(AF_INET,SOCK_STREAM,0);//建立一个套接字描述符,参数1表示通信的域IPv4协议,参数2表示通信类型流式套接字,参数3表示某种协议的特定类型如果只有一种类型为0
 if(s<0){
  perror("socket error\n");
  return -1;
}
//设置服务器地址
bzero(&server_addr,sizeof(server_addr));//清0
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//本地地址,可接受任意IP
server_addr.sin_port=htons(PORT);//服务器端口
inet_pton(AF_INET,argv[1],&server_addr.sin_addr);//用户输入IP地址
connect(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));//连接服务器端第一个参数是客户端套接字描述符,第二个参数是sockaddr指针,第三个参数是sockaddr的长度
process(s);//客户端接收到数据处理
close(s);//客户端关闭连接

}

void process(int s){
ssize_t size=0;
char buffer[1024];
for(;;){
size=read(0,buffer,1024);//从标准输入流读取数据
if(size>0){
 write(s,buffer,size);//发送给客户端
size=read(s,buffer,1024);//从客户端读取数据
write(1,buffer,size);//将数据写到标准输出

}
}

}


测试:

[root@localhost ~]# ./Server
port is 62361
hello

[root@localhost ~]# ./Client 127.0.0.1
hello
6 bytes

总结:

本文主要介绍了TCP网络数据发送与接收的流程,相关函数的使用,最后给出了一个具体的服务器与客户端通信的例子.


0 0