ftafta 2010-07-12
实现一个简单的Client/Server通信的程序:
服务器端和客户端的通信过程:
1. 服务器端:
1) 服务器端创建套接字(socket() )
2) 服务器端将套接字绑定到本机端口( bind() )
3) 服务器端的套接字转化为监听套接字( listen() )
4) 服务器端阻塞等待客户端发送信息到服务器( accept() )
5) 服务器端读取客户端的数据( read() )
6) 服务器端向客户端发送数据( write() )
7) 关闭连接(close() )
2.客户端:
1) 创建客户端套接字(socket() )
2) 向服务器端请求连接 (connect() )
3) 向服务器端写数据 (writ())
4) 接收服务器端的数据 (read ())
5) 关闭连接 (close())
编程常用数据结构和API:
1)定义套接字地址结构
# include<linux/socket.h>
struct sockaddr{ // 通用的套接字地址
unsigned short sa_family ; // 地址类型 AF_XXX
char sa_data[14]; // 协议地址
}
struct sockaddr_in{ //TCP/IP族的套接字地址
unsigned short sin_family; //地址类型
unsigned short int sin_port ; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充字节
struct in_addr{ // IP地址
unsigned long s_addr;
}
设置服务器地址的一个示例代码如下:
struct sockaddr_in sock;
memset(&sock,0,sizeof(sockaddr_in));
sock.sin_family=AF_INET;
sock.sin_port=80;
inet_aton("127.0.0.1",&sock.sin_addr)
其中,inet_aton 和 memset函数的定义如下:
#include<netinet/in.h>
#include<arpa/inet.h>
int inet_aton(const char *cp,struct in_addr *inp) //将cp所指向的字符串转化为二进制的网络字节顺序的IP地址
memset(void *s,int c,size_t n) //将s所指向的前n个字节赋成参数c所指定的值
2)创建套接字
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol)
// domain: AF_INET。。。
// type: 套接字类型 (SOCK_STREAM: TCP流套接字 | SOCK_DGRAM: UDP流套接字 )
// protocol =0
3) 绑定套接字(将创建套接字和服务器地址绑定)
#include <sys/types.h>
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr * my_addr, socklen_t addlen)
4) 建立监听(将套接字转换为监听套接字)
#include<sys/socket.h>
int listen(int s, int backlog)
//s: 套接字
//backlog : 定义连接请求队列的长度
5) 服务器端阻塞等待客户端连接请求
#include <sys/types>
#include <sys/socket>
int accept(int s, struct sockaddr_in * addr, socket_t addrlen);
6) 客户端请求连接
#inlcude <sys/types.h>
#inlcude <sys/socket.h>
int connect (int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
//sockfd:套接字
//serv_addr :服务器地址结构体
//addrlen : 地址结构体的长度
7) 接收数据
#inlcude <sys/types.h>
#inlcude <sys/socket.h>
size_t recv(int s, void * buf, size_t len, int flags);
//返回发送数据的长度
8) 发送数据
#inlcude <sys/types.h>
#inlcude <sys/socket.h>
size_t send(int s, const void* msg, size_t len, int flags);
//返回发送数据的长度
下面来看三个实例~
1) 基于TCP 协议的服务器端和客户端通信程序:
///服务器端 my_server.c
#include <sys/type.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include "my_recv.h" //自定义头文件
#define SERV_PORT 4507 // 服务器端口
#define LISTENQ 12 // 连接请求队列的最大长度
//发送数据
void send_data(int conn_fd,const char* string)
{
if( send(conn_fd,srtring,strlen(string),0)<0) {
// my_err("send",__LINE__); // my_err 函数在my_recv.h 中声明
printf("send error\n");
exit(1);
}
}
int main() {
int sock_fd,conn_fd;
int optval;
int ret;
int name_num;
pit_t pid;
socklen_t cli_len;
struct sockaddr_in cli_addr,serv_addr;
char recv_buf[128];
//创建TCP套接字
sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd<0){
my_err("socket",__LINE__);
}
//设置该套接字使之可以重新绑定端口
optval=1;
if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,(void *)optval,sizeof(int)<0){
// my_err("setsockopt",__LINE__);
printf("setsockopt error\n");
exit(1);
}
//初始化服务器端地址结构
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//将套接字绑定到本地端口
if (bind(sock_fd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in))<0){
//my_err("bind",__LINE__);
printf("bind error\n");
exit(1);
}
//将套接字转化为监听套接字
if (listen(sock_fd,LISTENQ)<0){
//my_err("listen",__LINE__);
printf("listen error\n");
exit(1);
}
cli_len=sizeof(struct sockaddr_in);
while(1){
//主进程通过accept阻塞等待客户端的连接请求,并返回连接套接字用于收发数据
conn_fd= accept(sock_fd,(struct sockaddr*)&cli_addr,&cli_len);
if(conn_fd<0){
//my_err("accept",__LINE__);
printf("accept error\n");
exit(1);
}
//创建子进程处理接收到的连接请求
if ((pid=fork())==0){ //子进程
while(1){
if((ret=recv(conn_fd,recv_buf,sizeof(recv_buf),0))<0)<0){
perror("recv");
exit(1);
}
printf("the received data is:%s \n",recv_buf);
send_data(conn_fd,"I've just received data: %s\n",recv_buf);
}
close(sock_fd);
close(conn_fd);
exit(0); //结束子进程
}
return 0;
}
///客户端 myclient.c
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<errno.h>
#include<arpa/inet.h>
int main(int argc,char **argv){
int i;
int ret;
int conn_fd;
int serv_port=4507;
char *strIP="127.0.0.1";
struct sockaddr_in serv_addr;
char input_buf[BUFSIZE]="Hello Joan!";
char recv_buf[BUFSIZE];
//初始化服务器端地址结构
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(serv_port);
inet_aton(strIP,&serv_addr.sin_addr);
//创建一个TCP套接字
conn_fd=socket(AF_INET,SOCK_STREAM,0);
if(conn_fd<0){
printf("client socket error\n");
exit(1);
}
//向服务器发送连接请求
if(connect(conn_fd,(struct sock_addr *)&serv_addr,sizeof(sock_addr))<0){
printf("client connect error\n");
exit(1);
}
//向服务器发送数据
if(send(conn_fd,input_buf,strlen(input_buf),0)<0){
printf("client send error\n");
exit(1);
}
// 读取服务器发送的数据
if((ret=recv(conn_fd,recv_buf,sizeof(recv_buf),0)<0){
printf("data too long\n");
exit(1);
}
printf("the received data from server is: %s\n",recv_buf);
close(conn_fd);
return 0;
}
2) 实现将一个文件分割为N份发送到服务器端,在服务器端再重组文件的程序:
3) 实现一个简单的端口扫描程序:
// 基于UDP 协议的服务器端和客户端通信程序: