1.TCP网络编程架构
TCP网络编程有两种模式,一种是服务器模式,另一种是客户端模式。服务器模式是通过建立一个服务监听客户端连接,当服务器收到客户端连接请求后,对请求进行处理;客户端模式是通过绑定服务器IP地址和端口,向服务器发送连接请求,并对服务器响应做出相应的处理。下图为TCP网络编程两种模式的架构图。
1 socket()介绍
int socket(int domain,int type,int protocol)
domain 是用来设置网络通信域的,也就是选择你所采用的协议族。如,AF_INET代表ipv4,AF_INET6代表ipv6.
type 是用来设置套接字通信类型的。常用的类型有SOCK_STREAM(流式套接字)支持TCP连接,提供可靠的、序列化的、双向的字节流;SOCK_DGRAM(数据包套接字),支持UPD连接,无连接不可靠。
protocol 使用来指定type类型中的某个类型。通常某个协议只有一个特定的类型,这样protocol设置为0;但是,也有默写协议不止一个特定类型,使用这个协议时,就需要指明所使用协议的具体特定类型。
socket 函数通过以上三个参数最终生成一个套接字文件描述符。
int sockfd = socket(AF_INET,SOCK_STREAM,0);
2 bind()介绍
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
首先介绍一下结构体sockaddr
struct sockaddr{
sa_family_t sa_family;//协议族
char sa_data[14];//协议族数据
}
其中,
sa_family_t类型其实就是unsigned short类型;
接下来介绍bind函数中参数:
sockfd 为套接字文件描述符;
myaddr 为指向套接字地址结构的指针。在进行sockfd绑定的时候,需要将IP地址,端口、协议类型等信息进行绑定。而sockaddr结构体不方便设置,所以在以太网中,一般采用结构体struct sockaddr_in进行设置:
struct sockaddr_in{
u8 sin_len; //结构体长度
u8 sin_family; //采用的协议族
u16 sin_port; //端口号,网络字节序
struct in_addr sin_addr; //IP地址32bits
char sin_zero[8]; //未用
}
结构体struct sockaddr 和结构体 struct sockaddr_in 大小相同,两者可以相互转化。可以说,
struct sockaddr_in
是
struct sockaddr的细化。addrlen 是myaddr的长度,addrlen = sizeof(
struct sockaddr
);
3 listen()介绍
int listen(int sockfd,int backlog);
在接受一个连接之前,需要使用listen来监听端口,backlog表示在accept()函数处理之前在等待队列中的客户端长度。
4 accept()介绍
当一个客户端请求到达服务器所监听的端口后,服务器会将此时的客户端请求加入到客户端等待队列中,直到使用accept()处理请求。accept()成功执行后,会返回一个客户端的套接字文件描述符,客户端的信息可以通过这个描述符来获得。
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
其中,根据addr可以获得IP、端口、协议类型等信息。 这里需要注意的是,addlen是一个指针,accept将其传给TCP/IP协议栈。
5 connect()介绍
客户端在建立套接字后,不需要绑定IP,端口等信息。客户端通过connect()函数连接服务器,connect()中指定了服务器IP地址、目的端口等信息。
int connect(int sockfd,struct sockaddr *, int addrlen);
6 write()介绍
当服务器收到一个客户端连接后,通过套接字描述符来进行数据写入。
int size = write(s,data,1024);
将data中的数据全部写入s中,返回值为成功写入的数据长度。
7 客户端服务器实现代码:
服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8888 /*侦听端口地址*/
#define BACKLOG 2 /*侦听队列长度*/
int main(int argc, char *argv[])
{
int ss,sc; /*ss为服务器的socket描述符,sc为客户端的socket描述符*/
struct sockaddr_in server_addr; /*服务器地址结构*/
struct sockaddr_in client_addr; /*客户端地址结构*/
int err; /*返回值*/
pid_t pid; /*分叉的进行ID*/
/*建立一个流式套接字*/
ss = socket(AF_INET, SOCK_STREAM, 0);
if(ss < 0){ /*出错*/
printf("socket error\n");
return -1;
}
/*设置服务器地址*/
bzero(&server_addr, sizeof(server_addr)); /*清零*/
server_addr.sin_family = AF_INET; /*协议族*/
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));
if(err < 0){/*出错*/
printf("bind error\n");
return -1;
}
/*设置侦听*/
err = listen(ss, BACKLOG);
if(err < 0){ /*出错*/
printf("listen error\n");
return -1;
}
/*主循环过程*/
for(;;) {
socklen_t addrlen = sizeof(struct sockaddr);
sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
/*接收客户端连接*/
if(sc < 0){ /*出错*/
continue; /*结束本次循环*/
}
/*建立一个新的进程处理到来的连接*/
pid = fork(); /*分叉进程*/
if( pid == 0 ){ /*子进程中*/
process_conn_server(sc); /*处理连接*/
close(ss); /*在子进程中关闭服务器的侦听*/
}else{
close(sc); /*在父进程中关闭客户端的连接*/
}
}
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888 /*侦听端口地址*/
int main(int argc, char *argv[])
{
int s; /*s为socket描述符*/
struct sockaddr_in server_addr; /*服务器地址结构*/
s = socket(AF_INET, SOCK_STREAM, 0); /*建立一个流式套接字 */
if(s < 0){ /*出错*/
printf("socket error\n");
return -1;
}
/*设置服务器地址*/
bzero(&server_addr, sizeof(server_addr)); /*清零*/
server_addr.sin_family = AF_INET; /*协议族*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
server_addr.sin_port = htons(PORT); /*服务器端口*/
/*将用户输入的字符串类型的IP地址转为整型*/
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
/*连接服务器*/
connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
process_conn_client(s); /*客户端处理过程*/
close(s); /*关闭连接*/
return 0;
}
完整代码见http://download.csdn.net/detail/qq_14976351/9630793