基础知识部分:http://www.cnblogs.com/Jimmy1988/p/7839940.html
1. 基本流程
Process | Client | Server | Comment |
---|---|---|---|
socket() | 创建socket文件描述符 | 同← | 通信双方都需建立 socket文件描述符是应用层通信接口 |
bind() | 可选 一般不绑定,由内核自动分配 |
必选 IP地址不能为公网地址 远程:一般设置为"0.0.0.0"或 INADDR_ANY 局域网:可设置为内网地址 |
详细可见: https://www.cnblogs.com/ok-lanyan/articles/2634242.html |
Listen() | 可选,一般不监听 | 必选 使socket处于监听网络状态 |
阻塞等待 |
connect() | 向服务端发起链接请求 | 包含目标主机的IP和Port | |
accept() | 服务端接受链接请求 接受链接后,使用一个新的socket描述符, 使服务端可接受多个链接 |
可以提取客户端的IP和Port | |
Send/Recv() | 进行数据包的发送和接受 | 同← | socket同样可以认为是文件, 可以直接read/write() |
close() | 关闭socket链接 | 同← | 可以分开关闭读/写操作 |
2. API
1). Socket()
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
/* Return Value:
* If successed: A file descriptor for new socket
* else : -1
*/
①. domain:
用于选择本次通信的协议簇(地址簇);
其详细定义见/usr/include/bits/socket.h
, 常用的宏如下:
Name | Purpose | Man page |
---|---|---|
AF_UNIX, AF_LOCAL |
Local communication | unix(7) |
AF_INET | IPv4 Internet protocols | ip(7) |
AF_INET6 | IPv6 Internet protocols | ipv6(7) |
AF_IPX | IPX - Novell | protocols |
AF_NETLINK | Kernel user interface device | netlink(7) |
②. type
协议类型
详细信息见/usr/include/bits/socket_types.h
,常用的如下:
Type | Purpose |
---|---|
SOCK_STREAM | Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. |
SOCK_DGRAM | Supports datagrams (connectionless, unreliable messages of a fixed maximum length). |
③. protocol
一般由系统自动选择,通常设置为0
2). bind()
当使用
socket()
创建一个socket后,存在着一个name space,但是没有内存地址与其关联;
本函数的目的就是为了分配内存地址给socket
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
/* Return Value:
* If successsed: 0
* else: -1
*/
①. socktfd
socket()函数返回的socket文件描述符
②. addr
分配给socket的地址空间首地址;
根据协议的不同,结构体定义不同,可以使用man 7 ip/ipv6/etc..
查看;
一般的定义形式如下:
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/*IF flags = AF_INET, 即IPv4*/
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
③. addrlen
地址长度,一般使用sizeof(xx)
3). Listen()
#include <sys/socket.h>
int listen(int sockfd, int backlog)
/*
* backlog: 请求排队的最大长度
*/
/* Return Value
* If successed: 0
* else: -1
*/
4). connect()
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* Return Value
* If successed: 0
* else: -1
*/
5). accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* Return Value
* If successed: 有效的接收到的socket描述符
* else: -1
*/
6). read/write()
读写时,可以把socket看成普通文件,因此读写的方式与普通文件相同:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
7). send/recv()
针对面向连接的socket的读写操作
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/* Arguments:
* 1. buf: 数据存放位置
* 2. len: 数据发送/接收大小
* 3. flags: 见下面
*/
/* Return Value
* If successed: 发送/接收的数据量
* else: -1
*/
**flags: **
enum
{
MSG_OOB = 0x01, /* Process out-of-band data. */
#define MSG_OOB MSG_OOB
MSG_PEEK = 0x02, /* Peek at incoming messages. */
#define MSG_PEEK MSG_PEEK
MSG_DONTROUTE = 0x04, /* Don't use local routing. */
#define MSG_DONTROUTE MSG_DONTROUTE
#ifdef __USE_GNU
/* DECnet uses a different name. */
MSG_TRYHARD = MSG_DONTROUTE,
# define MSG_TRYHARD MSG_DONTROUTE
#endif
MSG_CTRUNC = 0x08, /* Control data lost before delivery. */
#define MSG_CTRUNC MSG_CTRUNC
MSG_PROXY = 0x10, /* Supply or ask second address. */
#define MSG_PROXY MSG_PROXY
MSG_TRUNC = 0x20,
#define MSG_TRUNC MSG_TRUNC
MSG_DONTWAIT = 0x40, /* Nonblocking IO. */
#define MSG_DONTWAIT MSG_DONTWAIT
MSG_EOR = 0x80, /* End of record. */
#define MSG_EOR MSG_EOR
MSG_WAITALL = 0x100, /* Wait for a full request. */
#define MSG_WAITALL MSG_WAITALL
MSG_FIN = 0x200,
#define MSG_FIN MSG_FIN
MSG_SYN = 0x400,
#define MSG_SYN MSG_SYN
MSG_CONFIRM = 0x800, /* Confirm path validity. */
#define MSG_CONFIRM MSG_CONFIRM
MSG_RST = 0x1000,
#define MSG_RST MSG_RST
MSG_ERRQUEUE = 0x2000, /* Fetch message from error queue. */
#define MSG_ERRQUEUE MSG_ERRQUEUE
MSG_NOSIGNAL = 0x4000, /* Do not generate SIGPIPE. */
#define MSG_NOSIGNAL MSG_NOSIGNAL
MSG_MORE = 0x8000, /* Sender will send more. */
#define MSG_MORE MSG_MORE
MSG_WAITFORONE = 0x10000, /* Wait for at least one packet to return.*/
#define MSG_WAITFORONE MSG_WAITFORONE
MSG_CMSG_CLOEXEC = 0x40000000 /* Set close_on_exit for file
descriptor received through
SCM_RIGHTS. */
#define MSG_CMSG_CLOEXEC MSG_CMSG_CLOEXEC
};
8). close/shutdown()
两种方式关闭socket
I. close()
完全关闭socket通道,包括读和写
#include <unistd.h>
int close(int fd);
II. shutdown()
具备更强的灵活性,可选择性关闭读或写
#include <sys/socket.h>
int shutdown(int sockfd, int how);
how:
- how=0: 只关闭读通道;
- how=1: 只关闭写通道
- how=2: 关闭读写通道
9). getsockname/getpeername()
I. 获取socket的本地地址
要求:已完成绑定本地IP
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* Return Value
* If successed: 0
* else: -1
*/
II. 获取socket远程信息
要求:该socket已经完成远程连接
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* Return Value
* If successed: 0
* else: -1
*/
10). 获取本机IP地址等信息
#include <ifaddrs.h>
int getifaddrs(struct ifaddrs **ifap);
void freeifaddrs(struct ifaddrs *ifa)
/*Return value:
* If successed:0
* else: -1
*/
struct ifaddrs {
struct ifaddrs *ifa_next; /* Next item in list */
char *ifa_name; /* Name of interface */
unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
struct sockaddr *ifa_addr; /* Address of interface */
struct sockaddr *ifa_netmask; /* Netmask of interface */
union {
struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */
struct sockaddr *ifu_dstaddr;
/* Point-to-point destination address */
} ifa_ifu;
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
void *ifa_data; /* Address-specific data */
};
3. 示例代码
本次实现client/server两端的实时聊天功能,彼此不影响,可随时发送/接收对方数据;
当输入"quit”后,结束本次链接。
1. Client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
#define SIZE 1024
#define PORT 12345
//#define IP_ADDR "192.168.139.129"
#define IP_ADDR "139.196.121.132"
//#define IP_ADDR "47.89.246.154"
int sockfd;
void *snd(void)
{
int len = 0;
char buf[SIZE];
while(1)
{
memset(buf,' ', sizeof(buf));
printf("
Input msg send to server: ");
fgets(buf, SIZE, stdin);
if(buf[0] != ' ')
{
len = send(sockfd, buf, sizeof(buf), 0);
if(len < 0)
{
printf("SND: Some Error occured or disconnection!
");
break;
}
if(!strncasecmp(buf, "quit",4))
{
printf("SND: sub thread has quit!
");
break;
}
printf("
Send msg: %s
", buf);
}
}
pthread_exit(NULL);
}
void *rcv(void)
{
int len=0;
char buf[SIZE];
while(1)
{
memset(buf,' ', sizeof(buf));
len = recv(sockfd, buf, SIZE, 0);
if(len < 0)
{
printf("RCV: Some error occured!
");
break;
}
if(!strncasecmp(buf, "quit",4))
{
printf("RCV: sub thread has quit!
");
break;
}
printf("
The received msg:%s
", buf);
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
int ret;
//1. create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(EXIT_FAILURE);
}
//2. bind the ip addr
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(struct sockaddr_in));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(PORT);
dest_addr.sin_addr.s_addr = inet_addr(IP_ADDR);
/*
ret = bind(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in));
if(-1 == ret)
{
perror("bind");
exit(EXIT_FAILURE);
}
*/
//3. request the connection
ret = connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if(-1 == ret)
{
perror("connect");
exit(EXIT_FAILURE);
}
printf("Connect the server(IP:%s) successed!
", IP_ADDR);
//5. send/receive the message
pthread_t tid1, tid2;
if( 0 != pthread_create(&tid1, NULL, (void *)*snd, NULL))
{
perror("pthread_create");
exit(errno);
}
if( 0 != pthread_create(&tid2, NULL, (void *)*rcv, NULL))
{
perror("pthread_create");
exit(errno);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
2. Server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define SIZE 1024
#define PORT 12345
//#define IP_ADDR "192.168.139.129"
#define IP_ADDR "139.196.121.132"
int main(int argc, char *argv[])
{
int ret;
pthread_t tid;
int sockfd;
//1. create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(EXIT_FAILURE);
}
//2. bind the ip addr
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(struct sockaddr_in));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = inet_addr(IP_ADDR);
ret = bind(sockfd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
if(-1 == ret)
{
perror("bind");
exit(EXIT_FAILURE);
}
//3. listen the connection
unsigned int lisnum = 5;
ret = listen(sockfd, lisnum);
if(-1 == ret)
{
perror("listen");
exit(EXIT_FAILURE);
}
printf("Wait for client connection!
");
//4. accept the connection
int new_fd;
socklen_t len;
struct sockaddr_in client_addr;
new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
if(-1 == new_fd)
{
perror("accept");
exit(EXIT_FAILURE);
}
else
{
// printf("Server got connection:
Address:%s Port:%d Socket=%d
", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_fd);
}
//5. send/receive the message
char buf[SIZE];
ssize_t length;
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if(pid == 0)
{
while(1)
{
memset(buf, ' ', SIZE);
printf("Pls input the msg to send: ");
fgets(buf, SIZE, stdin);
if((length=strlen(buf)) < 0)
{
printf("send error, the length=%d
", length);
break;
}
length = send(new_fd, buf, sizeof(buf), 0);
if(!strncasecmp(buf, "quit", 4))
{
printf("Snd: close the connection!
");
break;
}
}
}
else
{
while(1)
{
memset(buf, ' ', SIZE);
length = recv(new_fd, buf, SIZE, 0);
if(length >= 0)
{
printf("
Receive msg: %s
", buf);
}
else
{
printf("Error occured
");
break;
}
if(!strncasecmp(buf, "quit", 4))
{
printf("Recv: close the connection!
");
break;
}
}
}
//Close the socket
close(sockfd);
close(new_fd);
return 0;
}