socket网络编程服务端和客户端
一、流程图
具体工作原理暂不分析。大致:
1.服务端:开始运行,阻塞accept(),等待客户端连接请求。
2.客户端:开始运行,发送连接请求给服务端,服务端收到后,建立连接。连接成功。
3.客户端:发送数据。服务端:接收数据,回到阻塞状态
4.连接结束。
二、具体代码
1.服务端
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sqlite3.h>
#include <arpa/inet.h>
#define BACKLOG 10
#define BUFFSIZE 4096
#define LISTEN_PORT 8889
int main(int argc,char *argv[])
{
int rv = -1;
int listen_fd = -1;
int client_fd = -1;
char buf[BUFFSIZE];
struct sockaddr_in svr_addr;
struct sockaddr_in client_addr;
socklen_t cliaddr_len;
listen_fd = socket(AF_INET, SOCK_STREAM, 0); //定义套接字
if(listen_fd < 0)
{
printf("creat socket failure:%s\n",strerror(errno));
return -1;
}
printf("socket create listen_fd[%d]\n",listen_fd);
memset(&svr_addr, 0, sizeof(svr_addr)); //先把sur_addr对应的内存空间清0
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(LISTEN_PORT);
svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
rv = bind(listen_fd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); //bind端口
if(rv < 0)
{
printf("bind error:%s\n",strerror(errno));
return -2;
}
else
{
printf("bind success\n");
}
listen(listen_fd, BACKLOG);
printf("star listen to port[%d]\n", LISTEN_PORT);
while(1)
{
printf("waiting for new client connection...\n");
client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &cliaddr_len);
//阻塞,等待客户端连接请求
if(client_fd < 0)
{
printf("accept new socket error:%s\n",strerror(errno));
return -3;
}
printf("Accpet new client [%s:%d] with fd [%d]\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
memset(buf, 0, sizeof(buf));
rv = read(client_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("read data from client socket [%d] error:%s\n", client_fd, strerror(errno));
close(client_fd);
continue;
}
else if(rv == 0)
{
printf("client socket [%d] disconnection\n", client_fd);
close(client_fd);
continue;
}
printf("read %d bytes data from client[%d]:%s\n", rv, client_fd, buf);
rv = write(client_fd, buf, rv);
if(rv < 0)
{
printf("write error:%s\n",strerror(errno));
close(client_fd);
}
sleep(5);
close(client_fd);
}
close(listen_fd);
return 0;
}
结果:
waiting for new client connection...
tianyujie@icloud-1st:~$ ./sev
socket create listen_fd[3]
bind success
star listen to port[8889]
waiting for new client connection...
2.客户端
代码如下
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SVR_IP "xxxxxxxx"//这里填写自己所连服务器的公网IP 或者127.0.0.1本机互联
#define SVR_PORT 8889
#define MSG "Here are client, call server!! call server!"
int main(int argc, char **argv)
{
int conn_fd = -1;
int rv = -1;
char buf[4096];
struct sockaddr_in svr_addr;
conn_fd = socket(AF_INET, SOCK_STREAM, 0);
if(conn_fd < 0)
{
printf("creat socket error:%s\n",strerror(errno));
return -1;
}
memset(&svr_addr, 0, sizeof(svr_addr));
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(SVR_PORT);
inet_aton(SVR_IP,&svr_addr.sin_addr);
rv = connect(conn_fd, (struct sockaddr*)&svr_addr, sizeof(svr_addr));
if(rv < 0)
{
printf("connect to server :%s:%d error:%s\n", SVR_IP, SVR_PORT, strerror(errno));
return 0;
}
rv = write(conn_fd, MSG, strlen(MSG));
if(rv < 0)
{
printf("connect to server [%s:%d] error:%s",SVR_IP, SVR_PORT, strerror(errno));
goto cleanap;
}
printf("send [%s] success\n", MSG);
rv = read(conn_fd, buf,sizeof(buf));
if(rv < 0)
{
printf("read from server error:%s\n",strerror(errno));
goto cleanap;
}
printf("read success:%s\n" ,buf);
cleanap:
close(conn_fd);
}
结果:
服务端接收消息:
函数分析
1.socket()
int socket(int af, int type, int protocol);
socket()用于创建一个socket描述符。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。创建socket的时候,也可以指定不同的参数创建不同的socket描述符。
参数:
af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM 和 SOCK_DGRAM
protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。当protocol为0时,会自动选择type类型对应的默认协议。
sockaddr
通用套接字 sockaddr 类型定义:
typedef unsigned short int sa_family_t;
struct sockaddr {
sa_family_t sa_family; /* 2 bytes address family, AF_xxx /
char sa_data[14]; / 14 bytes of protocol address */
}
ipv4对应的是sockaddr_in类型定义:
typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr {
uint32_t s_addr;
};
struct sockaddr_in {
sa_family_t sin_family; /* 2 bytes address family, AF_xxx such as AF_INET /
in_port_t sin_port; / 2 bytes port*/
struct in_addr sin_addr; /* 4 bytes IPv4 address*/
/* Pad to size of `struct sockaddr’. /
unsigned char sin_zero[8]; / 8 bytes unused padding data, always set be zero */
};
2.htons()
在将一个地址绑定到socket的时候,先将主机字节序转换成为网络字节序,
serv_addr.sin_port = htons(LISTEN_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
3.inet_aton()
将一个字符串IP地址转换为一个32位的网络序列IP地址
int inet_aton(const char string, struct in_addraddr);
参数描述:
1 输入参数string包含ASCII表示的IP地址。
2 输出参数addr是将要用新的IP地址更新的结构。
返回值:
如果这个函数成功,函数的返回值非零,如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以它的值会被忽略。
4.connect()和bind()
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
sock 为 socket 文件描述符,serv_addr为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出