Network Programming(三)

实例 一

Application client/serveur TCP
A和B通信,A向B发送message,B收到后,将这个msg在print出来
A是客户端,B是serveur

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
程序A
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MYMSGLEN 2000

int main(int argc,char* argv[])
{
int sock;
struct sockaddr_in server;
char message[MYMSGLEN],server_reply[MYMSGLEN];

sock=socket(AF_INET,SOCK_STREAM,0);
/*int socket(int domain,int type,int protocol);
* 创建一个通信endpoint,返回一个描述符(descriptor)
* 创建成功,一个对应新的socket的文件描述符(file descriptor)
* 错误,-1
*
* domain描述ptotocol family。这些families定义在<sys/socket.h>
* l例如:AF_INET:IPv4协议
*
* type指定服务类型:SOCK_StREAM:点对点控制流通信
* SOCK_DGRAM:未连接的通信
* SOCK_RAW:直接连间到协议(需要超级用户权限)
*
* protocol描述socket使用哪种协议,通常只有一种协议来支持socket类型,对应给协议簇
* 0:default
*/
if(sock==-1)
{
printf("Could not create socket\n");
return(-1);
}

printf("socket created\n");

/*int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
*服务器通过connect和server建立连接
*将client的socket->sockfd和server的地址->addr相关联。
*
*sockfd:用于连接的socket
*addr:被动参与者的地址(server的地址)
*addrlen:size of addr
*
*0,成功连接,否则-1
*/

server.sin_addr.s_addr=inet_addr("127.0.0.1");
server.sin_family=AF_INET;
server.sin_port=htons(8888);

int res;
if((res=connect(sock,(struct sockaddr*)&server,sizeof(server)))<0)
{
perror("Connect failed error\n");
close(sock);
return -1;
}

printf("Returned value,%d\n",res);
printf("connection established, waiting to be accepted ......");

while(1)
{
//void* memset(void* ptr,int value,size_t num);
//将ptr指的内存区域的前num个字节值都设置为value,然后返回指向ptr的指针
memset(message,0,MYMSGLEN);
printf("\nPlease type a message to transfer for processing:");
scanf("%s",message);


/*ssize_t send(int sockfd,const void *buf,size_t len,int flags)
* 将一个message传到另一个socket
* flags,特殊选项,通常都是0
* 成功,返回传送的字节数
* 失败 -1
*/
if(send(sock,message,strlen(message),0)<0)
{
printf("send failed\n");
close(sock);
return -1;
}

memset(server_reply,0,MYMSGLEN);
/*ssize_t recv(int sockfd,void* buf,size_t len,int flags);
* 和send差不多
*/
if(recv(sock,server_reply,MYMSGLEN,0)<0)
{
printf("recv failed\n");
close(sock);
return -1;
}

printf("Server reply:%s",server_reply);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
程序B
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MYMSGLEN 2000

int main(int argc,char** argv)
{
int socket_desc,client_sock;
int socket_size,read_size;
struct sockaddr_in server,client;
char client_message[MYMSGLEN];

socket_desc=socket(AF_INET,SOCK_STREAM,0);
if(socket_desc==-1)
{
perror("Could not create socket");
return -1;
}

printf("Socket created\n");

/*socket options
* int setsockopt(int socket,int level,int option_name,const void* option_value,socklen_toption_len);
* sock:将要被设置或者获取选项的套接字。
* level:选项所在的协议层。
* optname:需要访问的选项名。
* optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
* optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
* level指定控制套接字的层次.可以取三种值:
* 1)SOL_SOCKET:通用套接字选项.
* 2)IPPROTO_IP:IP选项.
* 3)IPPROTO_TCP:TCP选项. 
* optname指定控制的方式(选项的名称),我们下面详细解释 
* SO_REUSERADDR      允许重用本地地址和端口         int
*/
int code,enable=1;
code = setsockopt(socket_desc,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(int));
if(code==-1){
perror("setsockopr");
return 1;
}

server.sin_family=AF_INET;
server.sin_addr.s_addr=INADDR_ANY;
server.sin_port=htons(8888);


/*当一个socket创建后,我们需要将一个本地地址绑定到他
* TCP/IP协议需要一个IP地址和一个端口号对应每个端点地址
*
* int bind(int sock,struct sockaddr* addr,socklen_t len)
* sock:socket descriptor
* addr:用来指定的地址
* len:指定addr数据结构的size
* 返回0,success
* 返回-1,error
*
* addr是一个整体的数据类型,根据address family由不同
* 对于IPv4:
* struct sockaddr_in{
* sa_family_t sin_family; address family:AF_INET
* in_port_t sin_port; port
* struct in_addr sin_addr; internet address
* }
*
* struct in_addr{
* unit32_t s_addr;
* };
*
*/

if(bind(socket_desc,(struct sockaddr*)&server,sizeof(server))<0)
{
perror("bind failed. Error");
close(socket_desc);
return -1;
}
printf("bind done\n");

/*int listen(int sockfd,int backlog)
* sockfd:整数,是一个文件描述符指向类型为SOCK_STREAM的socket
* backlog:整数,主动参与者的数量
* listening 0
* error -1
*/
code=listen(socket_desc,2);

if(code==-1){
perror("listen");
exit;
}

printf("Waiting for incoming connections...\n");
socket_size=sizeof(struct sockaddr_in);

/* int accept(int sockfd,struct sockaddr* addr,socklent_t* addrlen)
* sockfd:整数,正在被监听的socket
* addr:struct sockaddr,主动参与者的地址
*/
client_sock=accept(socket_desc,(struct sockaddr*)&client,(socklen_t*)&socket_size);
if(client_sock<0)
{
close(socket_desc);
perror("accept failed");
return -1;
}
printf("Connection accepted\n");

while((read_size=recv(client_sock,client_message,MYMSGLEN,0))>0)
{
client_message[read_size]='\0';
printf("MSG received: %s,size of the message received %d\n",client_message,read_size);
write(client_sock,client_message,read_size);
}

if(read_size==0)
{
printf("client disconnected\n");
fflush(stdout);
}
else if(read_size==-1)
{
printf("recv failed\n");
}

close(socket_desc);
close(client_sock);

return 0;
}

客户端编程思路:

  1. 创建socket:int socket(int 协议簇,int 服务类型,int 协议 ), 返回对应该socket的文件描述符,失败,返回-1
  2. 建立连接:int connect(int 用于连接的socket ,struct sockaddr* 服务器的地址结构, socklen_t addr的大小),返回0,成功连接,否则-1
  3. 在循环中本地通过scanf输入msg,再发送msg:ssize_t send(int 已经连接的socket和connect中的一样,const void* 待发送的信息这里是字符串指针,size_t strlen(message), int 特殊选项一般为0 ),成功,返回传送的字节数,失败,-1
  4. 同样在这个循环中,跟在send后,接受server的回复:ssize_t recv()和send差不多
  5. 断开连接:close(socket);

服务器编程思路:

  1. 创建socket:int socket(int 协议簇,int 服务类型,int 协议 ), 返回对应该socket的文件描述符,失败,返回-1
    1.5 设置socket:int setsockopt(int 要设置的socket,int 选项所在协议层,int 需要访问的选项名,const void* 指向选项值的指针,socklen_t 选项的长度); 返回-1,设置失败
  2. 绑定地址:int bind(int 创建出来的socket,struct sockaddr* 自己设定好的地址,socklen_t len,地址长度sizeof)返回0,success。返回-1,error
  3. 开始监听:int listen(int 用于监听的socket,int 主动参与者数量)。0正在监听,-1 error
  4. 接受连接:int accept(int 用于监听的socket,struct sockaddr 主动参与者的地址通过应用传递在return语句之后被填充,socklent_t 地址长度),返回客户端的socket,-1 error
  5. 在循环中不停接受来自客户端的message,recv()同客户端一样
  6. 处理接收到的数据,这里就是输出
  7. 把message送回给client write(client_sock,client_message,read_size);
  8. 断开连接:close(socket);

实例二

Programme client HTTP
编写一个客户端程序,向指定网站发送请求并获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>

#define MYMSGLEN 2000

int main(int argc,char** argv)
{
int socket_desc;
struct sockaddr_in server;
char *message,server_reply[MYMSGLEN];

struct hostent *he;

struct in_addr inaddr;

socket_desc=socket(AF_INET,SOCK_STREAM,0);
if(socket_desc==-1)
{
perror("Could not create socket");
return -1;
}

printf("Socket created\n");

//把DNS主机名变成IP地址(使用DNS)
if((he=gethostbyname("httpd.apache.org"))==NULL)
{
perror("gethostbyname");
return 2;
}

memcpy((char*)&inaddr,he->h_addr,sizeof(struct in_addr));
printf("IP address for httpd.apache.org is %s \n",inet_ntoa(inaddr));

server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(inet_ntoa(inaddr));
server.sin_port=htons(80);

if(connect(socket_desc,(struct sockaddr*)&server,sizeof(server))<0)
{
printf("connect error");
return 1;
}
printf("Connection\n");

message="GET / HTTP/1.1\r\nhost: httpd.apache.org \r\n\r\n";

if(send(socket_desc,message,strlen(message),0)<0)
{
puts("Send failed");
return -1;
}
puts("message request sent");

int res;
memset(server_reply,0,MYMSGLEN);

while((res=recv(socket_desc,server_reply,MYMSGLEN,0))!=0)
{
if(res <0)
{
puts("recv failed");
break;
}
else
{
printf("%s",server_reply);
memset(server_reply,0,MYMSGLEN);
}
}

close(socket_desc);
return 0;
}

主机名和地址转换功能

  • struct hostent gethostbyname(const char hostname);
    把DNS主机名变成IP地址(使用DNS)
  • strcut hostent gethostbyaddr(const char addr,size_t len,int family);
    把IP地址变成DNS主机名
  • char* inet_ntoa(struct in_addr invader);
    将IP地址转换为ASCII点十进制符号
  • in_addr_t inet_addr(const char* strptr);
    将点分十进制变成IP地址,-1 failure,不能用于广播(255.255.255.2555)
  • int inet_aton(const char* sttrptr,struct in_addr inaddr);
    将点十进制表示法转换为IP地址;成功返回1,失败返回0
  • int inet_pton(int af,const char src, void dst);
    将IPv4和IPv6地址从文本转换为二进制形式
  • const char inet_ntop(int af,const void src,char* dst,socklen_t size);
    将IPv4和IPv6地址从二进制转换为文本形式

其他一些有用函数

  • void bzero(void* s,size_t n);
    将区域的前n个字节设置为从零开始
  • void memset(void s,int c,size_t n);
    用常量字节c填充指向的存储区的前n个字节
    返回指向内存区域s的指针
  • void memcpy(void dest,const void* src,size_t n);
    将n个字节从内存区域src复制到内存区域dest。内存区域不能重叠